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.js CHANGED
@@ -17,49 +17,8 @@ var __export = (target, all) => {
17
17
  __defProp(target, name, { get: all[name], enumerable: true });
18
18
  };
19
19
 
20
- // src/shared/utils/token-utils.ts
21
- function formatDeprecationMessage(token, description = "", format = "bracket") {
22
- if (token.$deprecated == null || token.$deprecated === false) {
23
- return description;
24
- }
25
- const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
26
- if (format === "comment") {
27
- const msg = deprecationMsg ? ` ${deprecationMsg}` : "";
28
- return `DEPRECATED${msg}`;
29
- } else {
30
- const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
31
- const prefix = `[DEPRECATED${msg}]`;
32
- return description ? `${prefix} ${description}` : prefix;
33
- }
34
- }
35
- function stripInternalTokenMetadata(tokens) {
36
- const cleaned = {};
37
- for (const [name, token] of Object.entries(tokens)) {
38
- const { _isAlias: _alias, _sourceModifier: _source, _sourceSet, ...rest } = token;
39
- cleaned[name] = rest;
40
- }
41
- return cleaned;
42
- }
43
- function getSortedTokenEntries(tokens) {
44
- return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
45
- }
46
- function isTokenLike(value) {
47
- return typeof value === "object" && value !== null && ("$value" in value || "$ref" in value);
48
- }
49
- function getPureAliasReferenceName(value) {
50
- if (typeof value !== "string") {
51
- return void 0;
52
- }
53
- const match = /^\{([^}]+)\}$/.exec(value);
54
- return match?.[1]?.trim();
55
- }
56
- var init_token_utils = __esm({
57
- "src/shared/utils/token-utils.ts"() {
58
- }
59
- });
60
-
61
20
  // src/shared/errors/index.ts
62
- var DispersaError, TokenReferenceError, CircularReferenceError, ValidationError, ColorParseError, DimensionFormatError, FileOperationError, ConfigurationError, BasePermutationError, ModifierError;
21
+ var DispersaError, TokenReferenceError, CircularReferenceError, ValidationError, FileOperationError, ConfigurationError, BasePermutationError, ModifierError;
63
22
  var init_errors = __esm({
64
23
  "src/shared/errors/index.ts"() {
65
24
  DispersaError = class extends Error {
@@ -110,20 +69,6 @@ var init_errors = __esm({
110
69
  this.name = "ValidationError";
111
70
  }
112
71
  };
113
- ColorParseError = class extends DispersaError {
114
- constructor(colorValue) {
115
- super(`Color parsing failed: '${colorValue}'. Provide a valid CSS color or DTCG color object.`);
116
- this.colorValue = colorValue;
117
- this.name = "ColorParseError";
118
- }
119
- };
120
- DimensionFormatError = class extends DispersaError {
121
- constructor(dimensionValue) {
122
- super(`Dimension parsing failed: '${dimensionValue}'. Provide a valid DTCG dimension object.`);
123
- this.dimensionValue = dimensionValue;
124
- this.name = "DimensionFormatError";
125
- }
126
- };
127
72
  FileOperationError = class extends DispersaError {
128
73
  constructor(operation, filePath, originalError) {
129
74
  super(`Failed to ${operation} file: ${filePath}. ${originalError.message}`);
@@ -159,6 +104,70 @@ var init_errors = __esm({
159
104
  }
160
105
  });
161
106
 
107
+ // src/shared/utils/token-utils.ts
108
+ function formatDeprecationMessage(token, description = "", format = "bracket") {
109
+ if (token.$deprecated == null || token.$deprecated === false) {
110
+ return description;
111
+ }
112
+ const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
113
+ if (format === "comment") {
114
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
115
+ return `DEPRECATED${msg2}`;
116
+ }
117
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
118
+ const prefix = `[DEPRECATED${msg}]`;
119
+ return description ? `${prefix} ${description}` : prefix;
120
+ }
121
+ function stripInternalTokenMetadata(tokens) {
122
+ const cleaned = {};
123
+ for (const [name, token] of Object.entries(tokens)) {
124
+ const { _isAlias: _alias, _sourceModifier: _source, _sourceSet, ...rest } = token;
125
+ cleaned[name] = rest;
126
+ }
127
+ return cleaned;
128
+ }
129
+ function getSortedTokenEntries(tokens) {
130
+ return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
131
+ }
132
+ function buildNestedTokenObject(tokens, extractValue) {
133
+ const result = {};
134
+ for (const [, token] of getSortedTokenEntries(tokens)) {
135
+ setNestedValue(result, token.path, extractValue(token));
136
+ }
137
+ return result;
138
+ }
139
+ function setNestedValue(root, path7, value) {
140
+ let current = root;
141
+ for (let i = 0; i < path7.length - 1; i++) {
142
+ const part = path7[i];
143
+ if (part == null) {
144
+ continue;
145
+ }
146
+ if (!(part in current)) {
147
+ current[part] = {};
148
+ }
149
+ current = current[part];
150
+ }
151
+ const lastPart = path7[path7.length - 1];
152
+ if (lastPart != null) {
153
+ current[lastPart] = value;
154
+ }
155
+ }
156
+ function isTokenLike(value) {
157
+ return typeof value === "object" && value !== null && ("$value" in value || "$ref" in value);
158
+ }
159
+ function getPureAliasReferenceName(value) {
160
+ if (typeof value !== "string") {
161
+ return void 0;
162
+ }
163
+ const match = /^\{([^}]+)\}$/.exec(value);
164
+ return match?.[1]?.trim();
165
+ }
166
+ var init_token_utils = __esm({
167
+ "src/shared/utils/token-utils.ts"() {
168
+ }
169
+ });
170
+
162
171
  // src/shared/utils/validation-handler.ts
163
172
  var ValidationHandler;
164
173
  var init_validation_handler = __esm({
@@ -2898,30 +2907,29 @@ var init_validator = __esm({
2898
2907
  validateTokenOrGroup(obj) {
2899
2908
  const hasValue = isTokenLike(obj);
2900
2909
  if (hasValue) {
2901
- const tokenErrors = this.validateToken(obj);
2902
- if (tokenErrors.length === 0) {
2910
+ const tokenErrors2 = this.validateToken(obj);
2911
+ if (tokenErrors2.length === 0) {
2903
2912
  return { type: "token", errors: [] };
2904
2913
  }
2905
2914
  return {
2906
2915
  type: "invalid",
2907
- errors: tokenErrors,
2916
+ errors: tokenErrors2,
2908
2917
  message: "Object has $value/$ref but failed token validation"
2909
2918
  };
2910
- } else {
2911
- const groupErrors = this.validateGroup(obj);
2912
- if (groupErrors.length === 0) {
2913
- return { type: "group", errors: [] };
2914
- }
2915
- const tokenErrors = this.validateToken(obj);
2916
- if (tokenErrors.length === 0) {
2917
- return { type: "token", errors: [] };
2918
- }
2919
- return {
2920
- type: "invalid",
2921
- errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2922
- message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2923
- };
2924
2919
  }
2920
+ const groupErrors = this.validateGroup(obj);
2921
+ if (groupErrors.length === 0) {
2922
+ return { type: "group", errors: [] };
2923
+ }
2924
+ const tokenErrors = this.validateToken(obj);
2925
+ if (tokenErrors.length === 0) {
2926
+ return { type: "token", errors: [] };
2927
+ }
2928
+ return {
2929
+ type: "invalid",
2930
+ errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2931
+ message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2932
+ };
2925
2933
  }
2926
2934
  /**
2927
2935
  * Format AJV errors into readable ValidationError objects
@@ -3116,35 +3124,34 @@ var init_resolver_parser = __esm({
3116
3124
  this.validateSourceReferences(set.sources, `set "${setName}"`, contextMsg);
3117
3125
  }
3118
3126
  }
3119
- if (resolver.modifiers) {
3120
- for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3121
- for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3122
- this.validateSourceReferences(
3123
- sources,
3124
- `modifier "${modifierName}" context "${contextName}"`,
3125
- contextMsg
3126
- );
3127
- }
3127
+ if (!resolver.modifiers) {
3128
+ return;
3129
+ }
3130
+ for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3131
+ for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3132
+ this.validateSourceReferences(
3133
+ sources,
3134
+ `modifier "${modifierName}" context "${contextName}"`,
3135
+ contextMsg
3136
+ );
3128
3137
  }
3129
3138
  }
3130
3139
  }
3131
- /**
3132
- * Validate source references in an array
3133
- */
3134
3140
  validateSourceReferences(sources, location, contextMsg) {
3135
3141
  for (const source of sources) {
3136
- if (typeof source === "object" && source !== null && "$ref" in source) {
3137
- const ref = source.$ref;
3138
- if (ref.startsWith("#/modifiers/")) {
3139
- this.handleValidationIssue(
3140
- `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3141
- );
3142
- }
3143
- if (ref.startsWith("#/resolutionOrder/")) {
3144
- this.handleValidationIssue(
3145
- `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3146
- );
3147
- }
3142
+ if (typeof source !== "object" || source === null || !("$ref" in source)) {
3143
+ continue;
3144
+ }
3145
+ const ref = source.$ref;
3146
+ if (ref.startsWith("#/modifiers/")) {
3147
+ this.handleValidationIssue(
3148
+ `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3149
+ );
3150
+ }
3151
+ if (ref.startsWith("#/resolutionOrder/")) {
3152
+ this.handleValidationIssue(
3153
+ `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3154
+ );
3148
3155
  }
3149
3156
  }
3150
3157
  }
@@ -3224,15 +3231,14 @@ var init_resolver_loader = __esm({
3224
3231
  * ```
3225
3232
  */
3226
3233
  async load(resolver) {
3227
- if (typeof resolver === "string") {
3228
- const absolutePath = path.resolve(this.options.baseDir, resolver);
3229
- const resolverDoc = await this.parser.parseFile(absolutePath);
3230
- const baseDir = path.dirname(absolutePath);
3231
- return { resolverDoc, baseDir };
3232
- } else {
3233
- const resolverDoc = this.parser.parseInline(resolver);
3234
- return { resolverDoc, baseDir: this.options.baseDir };
3234
+ if (typeof resolver !== "string") {
3235
+ const resolverDoc2 = this.parser.parseInline(resolver);
3236
+ return { resolverDoc: resolverDoc2, baseDir: this.options.baseDir };
3235
3237
  }
3238
+ const absolutePath = path.resolve(this.options.baseDir, resolver);
3239
+ const resolverDoc = await this.parser.parseFile(absolutePath);
3240
+ const baseDir = path.dirname(absolutePath);
3241
+ return { resolverDoc, baseDir };
3236
3242
  }
3237
3243
  /**
3238
3244
  * Load only the resolver document (without base directory)
@@ -3265,6 +3271,35 @@ function sanitizeDataAttributeName(value) {
3265
3271
  function escapeCssString(value) {
3266
3272
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
3267
3273
  }
3274
+ function groupTokensByType(tokens, typeGroupMap) {
3275
+ const groupMap = /* @__PURE__ */ new Map();
3276
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3277
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
3278
+ const existing = groupMap.get(groupName) ?? [];
3279
+ existing.push(token);
3280
+ groupMap.set(groupName, existing);
3281
+ }
3282
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
3283
+ name,
3284
+ tokens: groupTokens
3285
+ }));
3286
+ }
3287
+ function indentStr(width, level) {
3288
+ return " ".repeat(width * level);
3289
+ }
3290
+ function buildGeneratedFileHeader() {
3291
+ return [
3292
+ "// Generated by Dispersa - do not edit manually",
3293
+ "// https://github.com/timges/dispersa"
3294
+ ].join("\n");
3295
+ }
3296
+ function toSafeIdentifier(name, keywords, capitalize) {
3297
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
3298
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
3299
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
3300
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
3301
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
3302
+ }
3268
3303
  function normalizeModifierInputs(inputs) {
3269
3304
  const normalized = {};
3270
3305
  for (const [key, value] of Object.entries(inputs)) {
@@ -3272,6 +3307,14 @@ function normalizeModifierInputs(inputs) {
3272
3307
  }
3273
3308
  return normalized;
3274
3309
  }
3310
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
3311
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
3312
+ if (!outputFile && requiresFile) {
3313
+ throw new ConfigurationError(
3314
+ `Output "${outputName}": file is required for ${presetLabel} output`
3315
+ );
3316
+ }
3317
+ }
3275
3318
  function buildStablePermutationKey(modifierInputs, dimensions) {
3276
3319
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
3277
3320
  }
@@ -3321,14 +3364,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
3321
3364
  }
3322
3365
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3323
3366
  }
3324
- function buildInMemoryOutputKey(params) {
3325
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
3367
+ function isBasePermutation(modifierInputs, defaults) {
3326
3368
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3327
3369
  const normalizedDefaults = normalizeModifierInputs(defaults);
3328
- const isBase = Object.entries(normalizedDefaults).every(
3329
- ([key, value]) => normalizedInputs[key] === value
3370
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
3371
+ }
3372
+ function buildInMemoryOutputKey(params) {
3373
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
3374
+ const permutationKey = generatePermutationKey(
3375
+ modifierInputs,
3376
+ resolver,
3377
+ isBasePermutation(modifierInputs, defaults)
3330
3378
  );
3331
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
3332
3379
  return `${outputName}-${permutationKey}.${extension}`;
3333
3380
  }
3334
3381
  function buildMetadata(resolver) {
@@ -3450,6 +3497,7 @@ function resolveFileName(fileName, modifierInputs) {
3450
3497
  }
3451
3498
  var init_utils = __esm({
3452
3499
  "src/renderers/bundlers/utils.ts"() {
3500
+ init_errors();
3453
3501
  init_token_utils();
3454
3502
  }
3455
3503
  });
@@ -3459,36 +3507,38 @@ var js_exports = {};
3459
3507
  __export(js_exports, {
3460
3508
  bundleAsJsModule: () => bundleAsJsModule
3461
3509
  });
3510
+ function updateStringTracking(state, char) {
3511
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
3512
+ if (!state.inString) {
3513
+ state.inString = true;
3514
+ state.stringChar = char;
3515
+ } else if (char === state.stringChar) {
3516
+ state.inString = false;
3517
+ state.stringChar = "";
3518
+ }
3519
+ }
3520
+ state.escaped = !state.escaped && char === "\\";
3521
+ }
3462
3522
  function extractObjectFromJsModule(formattedJs) {
3463
3523
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
3464
3524
  if (!assignmentMatch) {
3465
3525
  return "{}";
3466
3526
  }
3467
3527
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
3528
+ const state = { inString: false, stringChar: "", escaped: false };
3468
3529
  let braceCount = 0;
3469
- let inString = false;
3470
- let stringChar = "";
3471
- let escaped = false;
3472
3530
  for (let i = startIndex; i < formattedJs.length; i++) {
3473
3531
  const char = formattedJs[i];
3474
- if (!escaped && (char === '"' || char === "'" || char === "`")) {
3475
- if (!inString) {
3476
- inString = true;
3477
- stringChar = char;
3478
- } else if (char === stringChar) {
3479
- inString = false;
3480
- stringChar = "";
3481
- }
3482
- }
3483
- escaped = !escaped && char === "\\";
3484
- if (!inString) {
3485
- if (char === "{") {
3486
- braceCount++;
3487
- } else if (char === "}") {
3488
- braceCount--;
3489
- if (braceCount === 0) {
3490
- return formattedJs.substring(startIndex, i + 1);
3491
- }
3532
+ updateStringTracking(state, char);
3533
+ if (state.inString) {
3534
+ continue;
3535
+ }
3536
+ if (char === "{") {
3537
+ braceCount++;
3538
+ } else if (char === "}") {
3539
+ braceCount--;
3540
+ if (braceCount === 0) {
3541
+ return formattedJs.substring(startIndex, i + 1);
3492
3542
  }
3493
3543
  }
3494
3544
  }
@@ -3585,22 +3635,19 @@ __export(json_exports, {
3585
3635
  bundleAsJson: () => bundleAsJson
3586
3636
  });
3587
3637
  async function bundleAsJson(bundleData, resolver, formatTokens) {
3638
+ if (!formatTokens) {
3639
+ throw new ConfigurationError("JSON formatter was not provided");
3640
+ }
3588
3641
  const metadata = buildMetadata(resolver);
3589
3642
  const tokens = {};
3590
3643
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
3591
3644
  const cleanTokens = stripInternalMetadata(tokenSet);
3592
- if (!formatTokens) {
3593
- throw new ConfigurationError("JSON formatter was not provided");
3594
- }
3595
3645
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3596
3646
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3597
3647
  const themeJson = await formatTokens(cleanTokens);
3598
3648
  tokens[key] = JSON.parse(themeJson);
3599
3649
  }
3600
- const bundle = {
3601
- _meta: metadata,
3602
- tokens
3603
- };
3650
+ const bundle = { _meta: metadata, tokens };
3604
3651
  return JSON.stringify(bundle, null, 2);
3605
3652
  }
3606
3653
  var init_json = __esm({
@@ -3666,15 +3713,15 @@ var TypeGenerator = class {
3666
3713
  const lines = [];
3667
3714
  if (names.length === 0) {
3668
3715
  lines.push(`export type ${typeName} = never`);
3669
- } else {
3670
- lines.push(`export type ${typeName} =`);
3671
- for (let i = 0; i < names.length; i++) {
3672
- const name = names[i];
3673
- if (name == null) {
3674
- continue;
3675
- }
3676
- lines.push(` | "${name}"`);
3716
+ return lines;
3717
+ }
3718
+ lines.push(`export type ${typeName} =`);
3719
+ for (let i = 0; i < names.length; i++) {
3720
+ const name = names[i];
3721
+ if (name == null) {
3722
+ continue;
3677
3723
  }
3724
+ lines.push(` | "${name}"`);
3678
3725
  }
3679
3726
  return lines;
3680
3727
  }
@@ -3700,15 +3747,10 @@ var TypeGenerator = class {
3700
3747
  generateStructureType(tokens, options) {
3701
3748
  const lines = [];
3702
3749
  const structure = this.buildNestedStructure(tokens);
3703
- if (options.exportType === "type") {
3704
- lines.push(`export type ${options.moduleName} = {`);
3705
- this.addStructureProperties(lines, structure, 1);
3706
- lines.push("}");
3707
- } else {
3708
- lines.push(`export interface ${options.moduleName} {`);
3709
- this.addStructureProperties(lines, structure, 1);
3710
- lines.push("}");
3711
- }
3750
+ const opener = options.exportType === "type" ? `export type ${options.moduleName} = {` : `export interface ${options.moduleName} {`;
3751
+ lines.push(opener);
3752
+ this.addStructureProperties(lines, structure, 1);
3753
+ lines.push("}");
3712
3754
  return lines;
3713
3755
  }
3714
3756
  /**
@@ -3742,20 +3784,20 @@ var TypeGenerator = class {
3742
3784
  /**
3743
3785
  * Add structure properties to lines
3744
3786
  */
3745
- addStructureProperties(lines, structure, indent2) {
3746
- const indentStr = " ".repeat(indent2);
3787
+ addStructureProperties(lines, structure, indent) {
3788
+ const indentStr2 = " ".repeat(indent);
3747
3789
  for (const [key, value] of Object.entries(structure)) {
3748
3790
  if (this.isToken(value)) {
3749
3791
  const token = value;
3750
3792
  if (token.$description) {
3751
- lines.push(`${indentStr}/** ${token.$description} */`);
3793
+ lines.push(`${indentStr2}/** ${token.$description} */`);
3752
3794
  }
3753
3795
  const valueType = this.inferValueType(token);
3754
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueType}`);
3796
+ lines.push(`${indentStr2}${this.quoteKey(key)}: ${valueType}`);
3755
3797
  } else {
3756
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
3757
- this.addStructureProperties(lines, value, indent2 + 1);
3758
- lines.push(`${indentStr}}`);
3798
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
3799
+ this.addStructureProperties(lines, value, indent + 1);
3800
+ lines.push(`${indentStr2}}`);
3759
3801
  }
3760
3802
  }
3761
3803
  }
@@ -3880,12 +3922,6 @@ function toBuildError(error, outputName) {
3880
3922
  if (error instanceof ValidationError) {
3881
3923
  return { message, code: "VALIDATION", severity: "error" };
3882
3924
  }
3883
- if (error instanceof ColorParseError) {
3884
- return { message, code: "COLOR_PARSE", severity: "error" };
3885
- }
3886
- if (error instanceof DimensionFormatError) {
3887
- return { message, code: "DIMENSION_FORMAT", severity: "error" };
3888
- }
3889
3925
  if (error instanceof FileOperationError) {
3890
3926
  return { message, code: "FILE_OPERATION", path: error.filePath, severity: "error" };
3891
3927
  }
@@ -3953,28 +3989,27 @@ var BuildOrchestrator = class {
3953
3989
  if (config.hooks?.onBuildStart) {
3954
3990
  await config.hooks.onBuildStart({ config, resolver });
3955
3991
  }
3956
- let permutations;
3957
- if (config.permutations && config.permutations.length > 0) {
3958
- permutations = await Promise.all(
3959
- config.permutations.map(async (modifierInputs) => {
3960
- const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
3961
- resolver,
3962
- modifierInputs,
3963
- config.transforms,
3964
- config.preprocessors,
3965
- config.filters
3966
- );
3967
- return { tokens, modifierInputs: resolvedInputs };
3968
- })
3969
- );
3970
- } else {
3971
- permutations = await this.pipeline.resolveAllPermutations(
3992
+ if (!config.permutations || config.permutations.length === 0) {
3993
+ const permutations2 = await this.pipeline.resolveAllPermutations(
3972
3994
  resolver,
3973
3995
  config.transforms,
3974
3996
  config.preprocessors,
3975
3997
  config.filters
3976
3998
  );
3999
+ return this.executeBuild(buildPath, config, permutations2, resolver);
3977
4000
  }
4001
+ const permutations = await Promise.all(
4002
+ config.permutations.map(async (modifierInputs) => {
4003
+ const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
4004
+ resolver,
4005
+ modifierInputs,
4006
+ config.transforms,
4007
+ config.preprocessors,
4008
+ config.filters
4009
+ );
4010
+ return { tokens, modifierInputs: resolvedInputs };
4011
+ })
4012
+ );
3978
4013
  return this.executeBuild(buildPath, config, permutations, resolver);
3979
4014
  }
3980
4015
  /**
@@ -3996,44 +4031,15 @@ var BuildOrchestrator = class {
3996
4031
  * @returns Build result with success status, outputs, and any errors
3997
4032
  */
3998
4033
  async executeBuild(buildPath, config, permutations, resolver) {
3999
- const outputs = [];
4000
- const errors = [];
4001
4034
  try {
4002
4035
  const resolverDoc = await resolveResolverDocument(resolver);
4003
4036
  const metadata = buildMetadata(resolverDoc);
4004
- const basePermutation = metadata.defaults;
4005
4037
  const settled = await Promise.allSettled(
4006
- config.outputs.map(async (output) => {
4007
- if (output.hooks?.onBuildStart) {
4008
- await output.hooks.onBuildStart({ config, resolver });
4009
- }
4010
- try {
4011
- const results = await this.processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath);
4012
- if (output.hooks?.onBuildEnd) {
4013
- await output.hooks.onBuildEnd({ success: true, outputs: results });
4014
- }
4015
- return results;
4016
- } catch (error) {
4017
- if (output.hooks?.onBuildEnd) {
4018
- await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
4019
- }
4020
- throw error;
4021
- }
4022
- })
4038
+ config.outputs.map(
4039
+ (output) => this.buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver)
4040
+ )
4023
4041
  );
4024
- for (let i = 0; i < settled.length; i++) {
4025
- const outcome = settled[i];
4026
- if (outcome.status === "fulfilled") {
4027
- outputs.push(...outcome.value);
4028
- } else {
4029
- errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4030
- }
4031
- }
4032
- const result = {
4033
- success: errors.length === 0,
4034
- outputs,
4035
- errors: errors.length > 0 ? errors : void 0
4036
- };
4042
+ const result = this.collectSettledResults(settled, config);
4037
4043
  if (config.hooks?.onBuildEnd) {
4038
4044
  await config.hooks.onBuildEnd(result);
4039
4045
  }
@@ -4050,6 +4056,40 @@ var BuildOrchestrator = class {
4050
4056
  return result;
4051
4057
  }
4052
4058
  }
4059
+ async buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver) {
4060
+ if (output.hooks?.onBuildStart) {
4061
+ await output.hooks.onBuildStart({ config, resolver });
4062
+ }
4063
+ try {
4064
+ const results = await this.processOutput(output, permutations, resolverDoc, metadata, metadata.defaults, buildPath);
4065
+ if (output.hooks?.onBuildEnd) {
4066
+ await output.hooks.onBuildEnd({ success: true, outputs: results });
4067
+ }
4068
+ return results;
4069
+ } catch (error) {
4070
+ if (output.hooks?.onBuildEnd) {
4071
+ await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
4072
+ }
4073
+ throw error;
4074
+ }
4075
+ }
4076
+ collectSettledResults(settled, config) {
4077
+ const outputs = [];
4078
+ const errors = [];
4079
+ for (let i = 0; i < settled.length; i++) {
4080
+ const outcome = settled[i];
4081
+ if (outcome.status === "fulfilled") {
4082
+ outputs.push(...outcome.value);
4083
+ } else {
4084
+ errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4085
+ }
4086
+ }
4087
+ return {
4088
+ success: errors.length === 0,
4089
+ outputs,
4090
+ errors: errors.length > 0 ? errors : void 0
4091
+ };
4092
+ }
4053
4093
  async processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath) {
4054
4094
  if (!output.renderer.format) {
4055
4095
  throw new ConfigurationError("Renderer does not implement format()");
@@ -4099,7 +4139,7 @@ async function writeOutputFile(fileName, content, encoding = "utf-8") {
4099
4139
  }
4100
4140
  }
4101
4141
 
4102
- // src/processing/token-modifier.ts
4142
+ // src/processing/apply.ts
4103
4143
  function applyTransforms(tokens, transformList) {
4104
4144
  const result = {};
4105
4145
  for (const [name, token] of Object.entries(tokens)) {
@@ -5377,14 +5417,15 @@ var ResolutionEngine = class {
5377
5417
  mergeTokens(target, source) {
5378
5418
  const result = { ...target };
5379
5419
  for (const [key, value] of Object.entries(source)) {
5380
- if (typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value)) {
5381
- result[key] = this.mergeTokens(
5382
- result[key] ?? {},
5383
- value
5384
- );
5385
- } else {
5420
+ const isGroup = typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value);
5421
+ if (!isGroup) {
5386
5422
  result[key] = value;
5423
+ continue;
5387
5424
  }
5425
+ result[key] = this.mergeTokens(
5426
+ result[key] ?? {},
5427
+ value
5428
+ );
5388
5429
  }
5389
5430
  return result;
5390
5431
  }
@@ -6530,7 +6571,7 @@ function colorObjectToHex(color) {
6530
6571
  return formatHex(culoriColor);
6531
6572
  }
6532
6573
 
6533
- // src/processing/processors/transforms/built-in/dimension-converter.ts
6574
+ // src/processing/transforms/built-in/dimension-converter.ts
6534
6575
  function isDimensionObject(value) {
6535
6576
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
6536
6577
  }
@@ -6538,6 +6579,14 @@ function dimensionObjectToString(dimension) {
6538
6579
  return `${dimension.value}${dimension.unit}`;
6539
6580
  }
6540
6581
 
6582
+ // src/processing/transforms/built-in/duration-converter.ts
6583
+ function isDurationObject(value) {
6584
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
6585
+ }
6586
+ function durationObjectToString(duration) {
6587
+ return `${duration.value}${duration.unit}`;
6588
+ }
6589
+
6541
6590
  // src/renderers/android.ts
6542
6591
  init_errors();
6543
6592
  init_token_utils();
@@ -6589,9 +6638,6 @@ function resolveColorFormat(format) {
6589
6638
  }
6590
6639
  return "argb_hex";
6591
6640
  }
6592
- function indent(width, level) {
6593
- return " ".repeat(width * level);
6594
- }
6595
6641
  function escapeKotlinString(str) {
6596
6642
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
6597
6643
  }
@@ -6607,22 +6653,6 @@ function roundComponent(value) {
6607
6653
  function toResourceName(family) {
6608
6654
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
6609
6655
  }
6610
- function toPascalCase(name) {
6611
- const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6612
- const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
6613
- if (/^\d/.test(result)) {
6614
- return `_${result}`;
6615
- }
6616
- return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
6617
- }
6618
- function toKotlinIdentifier(name) {
6619
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6620
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
6621
- if (/^\d/.test(identifier)) {
6622
- return `_${identifier}`;
6623
- }
6624
- return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
6625
- }
6626
6656
  var AndroidRenderer = class {
6627
6657
  async format(context, options) {
6628
6658
  if (!options?.packageName) {
@@ -6630,6 +6660,7 @@ var AndroidRenderer = class {
6630
6660
  `Output "${context.output.name}": packageName is required for Android output`
6631
6661
  );
6632
6662
  }
6663
+ const visibility = options?.visibility;
6633
6664
  const opts = {
6634
6665
  preset: options?.preset ?? "standalone",
6635
6666
  packageName: options.packageName,
@@ -6637,7 +6668,8 @@ var AndroidRenderer = class {
6637
6668
  colorFormat: resolveColorFormat(options?.colorFormat),
6638
6669
  colorSpace: options?.colorSpace ?? "sRGB",
6639
6670
  structure: options?.structure ?? "nested",
6640
- visibility: options?.visibility,
6671
+ visibility,
6672
+ visPrefix: visibility ? `${visibility} ` : "",
6641
6673
  indent: options?.indent ?? 4
6642
6674
  };
6643
6675
  if (opts.preset === "bundle") {
@@ -6670,19 +6702,6 @@ var AndroidRenderer = class {
6670
6702
  // -----------------------------------------------------------------------
6671
6703
  // Flat structure grouping
6672
6704
  // -----------------------------------------------------------------------
6673
- groupTokensByType(tokens) {
6674
- const groupMap = /* @__PURE__ */ new Map();
6675
- for (const [, token] of getSortedTokenEntries(tokens)) {
6676
- const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
6677
- const existing = groupMap.get(groupName) ?? [];
6678
- existing.push(token);
6679
- groupMap.set(groupName, existing);
6680
- }
6681
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
6682
- name,
6683
- tokens: groupTokens
6684
- }));
6685
- }
6686
6705
  /**
6687
6706
  * Builds a flattened camelCase name from a token's path, stripping the
6688
6707
  * type prefix segment (which is already represented by the group object).
@@ -6691,7 +6710,7 @@ var AndroidRenderer = class {
6691
6710
  const path7 = token.path;
6692
6711
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
6693
6712
  const joined = withoutTypePrefix.join("_");
6694
- return toKotlinIdentifier(joined);
6713
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
6695
6714
  }
6696
6715
  // -----------------------------------------------------------------------
6697
6716
  // Rendering
@@ -6703,22 +6722,21 @@ var AndroidRenderer = class {
6703
6722
  return this.formatAsNested(tokens, options);
6704
6723
  }
6705
6724
  formatAsNested(tokens, options) {
6725
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6706
6726
  const tree = this.buildTokenTree(tokens);
6707
- const tokenTypes = /* @__PURE__ */ new Set();
6708
- this.collectTokenTypes(tree, tokenTypes);
6709
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6727
+ return this.buildFile(tokenTypes, options, (lines) => {
6710
6728
  lines.push(`@Suppress("unused")`);
6711
- lines.push(`${vis}object ${options.objectName} {`);
6729
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6712
6730
  this.renderTreeChildren(lines, tree, 1, options);
6713
6731
  lines.push("}");
6714
6732
  });
6715
6733
  }
6716
6734
  formatAsFlat(tokens, options) {
6717
- const groups = this.groupTokensByType(tokens);
6735
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
6718
6736
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6719
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6737
+ return this.buildFile(tokenTypes, options, (lines) => {
6720
6738
  lines.push(`@Suppress("unused")`);
6721
- lines.push(`${vis}object ${options.objectName} {`);
6739
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6722
6740
  this.renderFlatGroups(lines, groups, 1, options);
6723
6741
  lines.push("}");
6724
6742
  });
@@ -6729,9 +6747,8 @@ var AndroidRenderer = class {
6729
6747
  */
6730
6748
  buildFile(tokenTypes, options, renderBody) {
6731
6749
  const imports = this.collectImports(tokenTypes, options);
6732
- const vis = options.visibility ? `${options.visibility} ` : "";
6733
6750
  const lines = [];
6734
- lines.push(this.buildFileHeader());
6751
+ lines.push(buildGeneratedFileHeader());
6735
6752
  lines.push("");
6736
6753
  lines.push(`package ${options.packageName}`);
6737
6754
  lines.push("");
@@ -6742,19 +6759,18 @@ var AndroidRenderer = class {
6742
6759
  lines.push("");
6743
6760
  }
6744
6761
  if (tokenTypes.has("shadow")) {
6745
- lines.push(...this.buildShadowTokenClass(vis, options));
6762
+ lines.push(...this.buildShadowTokenClass(options));
6746
6763
  lines.push("");
6747
6764
  }
6748
- renderBody(lines, vis);
6765
+ renderBody(lines);
6749
6766
  lines.push("");
6750
6767
  return lines.join("\n");
6751
6768
  }
6752
6769
  renderFlatGroups(lines, groups, baseDepth, options) {
6753
- const vis = options.visibility ? `${options.visibility} ` : "";
6754
- const groupIndent = indent(options.indent, baseDepth);
6755
- const valIndent = indent(options.indent, baseDepth + 1);
6770
+ const groupIndent = indentStr(options.indent, baseDepth);
6771
+ const valIndent = indentStr(options.indent, baseDepth + 1);
6756
6772
  for (const group of groups) {
6757
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
6773
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
6758
6774
  for (const token of group.tokens) {
6759
6775
  const kotlinName = this.buildFlatKotlinName(token);
6760
6776
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -6762,23 +6778,24 @@ var AndroidRenderer = class {
6762
6778
  if (token.$description) {
6763
6779
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
6764
6780
  }
6765
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6781
+ lines.push(
6782
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
6783
+ );
6766
6784
  }
6767
6785
  lines.push(`${groupIndent}}`);
6768
6786
  lines.push("");
6769
6787
  }
6770
6788
  }
6771
6789
  renderTreeChildren(lines, node, depth, options) {
6772
- const vis = options.visibility ? `${options.visibility} ` : "";
6773
- const pad = indent(options.indent, depth);
6790
+ const pad = indentStr(options.indent, depth);
6774
6791
  const entries = Array.from(node.children.entries());
6775
6792
  for (let idx = 0; idx < entries.length; idx++) {
6776
6793
  const [key, child] = entries[idx];
6777
6794
  if (child.token && child.children.size === 0) {
6778
6795
  this.renderLeaf(lines, key, child.token, depth, options);
6779
6796
  } else if (child.children.size > 0 && !child.token) {
6780
- const objectName = toPascalCase(key);
6781
- lines.push(`${pad}${vis}object ${objectName} {`);
6797
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
6798
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
6782
6799
  this.renderTreeChildren(lines, child, depth + 1, options);
6783
6800
  lines.push(`${pad}}`);
6784
6801
  if (idx < entries.length - 1) {
@@ -6791,30 +6808,23 @@ var AndroidRenderer = class {
6791
6808
  }
6792
6809
  }
6793
6810
  renderLeaf(lines, key, token, depth, options) {
6794
- const vis = options.visibility ? `${options.visibility} ` : "";
6795
- const pad = indent(options.indent, depth);
6796
- const kotlinName = toKotlinIdentifier(key);
6811
+ const pad = indentStr(options.indent, depth);
6812
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
6797
6813
  const kotlinValue = this.formatKotlinValue(token, options, depth);
6798
6814
  const annotation = this.typeAnnotationSuffix(token);
6799
6815
  if (token.$description) {
6800
6816
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
6801
6817
  }
6802
- lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6803
- }
6804
- buildFileHeader() {
6805
- return [
6806
- "// Generated by Dispersa - do not edit manually",
6807
- "// https://github.com/timges/dispersa"
6808
- ].join("\n");
6818
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
6809
6819
  }
6810
6820
  // -----------------------------------------------------------------------
6811
6821
  // Shadow data class
6812
6822
  // -----------------------------------------------------------------------
6813
- buildShadowTokenClass(vis, options) {
6814
- const i1 = indent(options.indent, 1);
6823
+ buildShadowTokenClass(options) {
6824
+ const i1 = indentStr(options.indent, 1);
6815
6825
  return [
6816
6826
  "@Immutable",
6817
- `${vis}data class ShadowToken(`,
6827
+ `${options.visPrefix}data class ShadowToken(`,
6818
6828
  `${i1}val color: Color,`,
6819
6829
  `${i1}val elevation: Dp,`,
6820
6830
  `${i1}val offsetX: Dp,`,
@@ -6865,14 +6875,6 @@ var AndroidRenderer = class {
6865
6875
  }
6866
6876
  return Array.from(imports).sort();
6867
6877
  }
6868
- collectTokenTypes(node, types) {
6869
- if (node.token?.$type) {
6870
- types.add(node.token.$type);
6871
- }
6872
- for (const child of node.children.values()) {
6873
- this.collectTokenTypes(child, types);
6874
- }
6875
- }
6876
6878
  collectTokenTypesFromEntries(tokens) {
6877
6879
  const types = /* @__PURE__ */ new Set();
6878
6880
  for (const [, token] of Object.entries(tokens)) {
@@ -7099,9 +7101,8 @@ var AndroidRenderer = class {
7099
7101
  return map[name.toLowerCase()];
7100
7102
  }
7101
7103
  formatDurationValue(value) {
7102
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
7103
- const dur = value;
7104
- return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
7104
+ if (isDurationObject(value)) {
7105
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
7105
7106
  }
7106
7107
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
7107
7108
  }
@@ -7119,8 +7120,8 @@ var AndroidRenderer = class {
7119
7120
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
7120
7121
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
7121
7122
  const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
7122
- const propIndent = indent(options.indent, depth + 1);
7123
- const closeIndent = indent(options.indent, depth);
7123
+ const propIndent = indentStr(options.indent, depth + 1);
7124
+ const closeIndent = indentStr(options.indent, depth);
7124
7125
  return [
7125
7126
  "ShadowToken(",
7126
7127
  `${propIndent}color = ${color},`,
@@ -7169,8 +7170,8 @@ var AndroidRenderer = class {
7169
7170
  if (parts.length === 0) {
7170
7171
  return "TextStyle()";
7171
7172
  }
7172
- const propIndent = indent(options.indent, depth + 1);
7173
- const closeIndent = indent(options.indent, depth);
7173
+ const propIndent = indentStr(options.indent, depth + 1);
7174
+ const closeIndent = indentStr(options.indent, depth);
7174
7175
  return `TextStyle(
7175
7176
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
7176
7177
  ${closeIndent})`;
@@ -7179,12 +7180,12 @@ ${closeIndent})`;
7179
7180
  // Output: standalone
7180
7181
  // -----------------------------------------------------------------------
7181
7182
  async formatStandalone(context, options) {
7182
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7183
- if (!context.output.file && requiresFile) {
7184
- throw new ConfigurationError(
7185
- `Output "${context.output.name}": file is required for standalone Android output`
7186
- );
7187
- }
7183
+ assertFileRequired(
7184
+ context.buildPath,
7185
+ context.output.file,
7186
+ context.output.name,
7187
+ "standalone Android"
7188
+ );
7188
7189
  const files = {};
7189
7190
  for (const { tokens, modifierInputs } of context.permutations) {
7190
7191
  const processedTokens = stripInternalMetadata(tokens);
@@ -7204,12 +7205,12 @@ ${closeIndent})`;
7204
7205
  // Output: bundle
7205
7206
  // -----------------------------------------------------------------------
7206
7207
  async formatBundle(context, options) {
7207
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7208
- if (!context.output.file && requiresFile) {
7209
- throw new ConfigurationError(
7210
- `Output "${context.output.name}": file is required for bundle Android output`
7211
- );
7212
- }
7208
+ assertFileRequired(
7209
+ context.buildPath,
7210
+ context.output.file,
7211
+ context.output.name,
7212
+ "bundle Android"
7213
+ );
7213
7214
  const content = this.formatBundleContent(context, options);
7214
7215
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
7215
7216
  outputName: context.output.name,
@@ -7222,15 +7223,15 @@ ${closeIndent})`;
7222
7223
  }
7223
7224
  formatBundleContent(context, options) {
7224
7225
  const allTokenTypes = this.collectAllPermutationTypes(context);
7225
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
7226
- const i1 = indent(options.indent, 1);
7226
+ return this.buildFile(allTokenTypes, options, (lines) => {
7227
+ const i1 = indentStr(options.indent, 1);
7227
7228
  lines.push(`@Suppress("unused")`);
7228
- lines.push(`${vis}object ${options.objectName} {`);
7229
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
7229
7230
  for (let idx = 0; idx < context.permutations.length; idx++) {
7230
7231
  const { tokens, modifierInputs } = context.permutations[idx];
7231
7232
  const processedTokens = stripInternalMetadata(tokens);
7232
7233
  const permName = this.buildPermutationName(modifierInputs);
7233
- lines.push(`${i1}${vis}object ${permName} {`);
7234
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
7234
7235
  this.renderBundleTokens(lines, processedTokens, options, 2);
7235
7236
  lines.push(`${i1}}`);
7236
7237
  if (idx < context.permutations.length - 1) {
@@ -7241,20 +7242,17 @@ ${closeIndent})`;
7241
7242
  });
7242
7243
  }
7243
7244
  collectAllPermutationTypes(context) {
7244
- const allTokenTypes = /* @__PURE__ */ new Set();
7245
+ const types = /* @__PURE__ */ new Set();
7245
7246
  for (const { tokens } of context.permutations) {
7246
- const processed = stripInternalMetadata(tokens);
7247
- for (const [, token] of Object.entries(processed)) {
7248
- if (token.$type) {
7249
- allTokenTypes.add(token.$type);
7250
- }
7247
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
7248
+ types.add(t);
7251
7249
  }
7252
7250
  }
7253
- return allTokenTypes;
7251
+ return types;
7254
7252
  }
7255
7253
  renderBundleTokens(lines, tokens, options, baseDepth) {
7256
7254
  if (options.structure === "flat") {
7257
- const groups = this.groupTokensByType(tokens);
7255
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
7258
7256
  this.renderFlatGroups(lines, groups, baseDepth, options);
7259
7257
  return;
7260
7258
  }
@@ -7266,7 +7264,7 @@ ${closeIndent})`;
7266
7264
  if (values.length === 0) {
7267
7265
  return "Default";
7268
7266
  }
7269
- return values.map((v) => toPascalCase(v)).join("");
7267
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
7270
7268
  }
7271
7269
  };
7272
7270
  function androidRenderer() {
@@ -7286,19 +7284,19 @@ init_token_utils();
7286
7284
  // src/renderers/bundlers/css.ts
7287
7285
  init_errors();
7288
7286
  init_utils();
7287
+ var REF_PREFIX_SETS = "#/sets/";
7288
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
7289
7289
  var getSourceSet = (token) => {
7290
7290
  if (typeof token !== "object" || token === null) {
7291
7291
  return void 0;
7292
7292
  }
7293
- const maybe = token;
7294
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
7293
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
7295
7294
  };
7296
7295
  var getSourceModifier = (token) => {
7297
7296
  if (typeof token !== "object" || token === null) {
7298
7297
  return void 0;
7299
7298
  }
7300
- const maybe = token;
7301
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
7299
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
7302
7300
  };
7303
7301
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
7304
7302
  const baseItem = bundleData.find((item) => item.isBase);
@@ -7383,6 +7381,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
7383
7381
  return `/* Modifier: ${modifier}=${context} */
7384
7382
  ${css2}`;
7385
7383
  }
7384
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
7385
+ if (Object.keys(blockTokens).length === 0) {
7386
+ return;
7387
+ }
7388
+ for (const k of Object.keys(blockTokens)) {
7389
+ included.add(k);
7390
+ }
7391
+ blocks.push({ key, description, tokens: blockTokens });
7392
+ }
7386
7393
  function collectSetTokens(tokens, setName, included) {
7387
7394
  const result = {};
7388
7395
  for (const [name, token] of Object.entries(tokens)) {
@@ -7413,75 +7420,67 @@ function collectRemainder(tokens, included) {
7413
7420
  function buildSetLayerBlocks(tokens, resolver) {
7414
7421
  const blocks = [];
7415
7422
  const included = /* @__PURE__ */ new Set();
7416
- const addBlock = (key, blockTokens, description) => {
7417
- if (Object.keys(blockTokens).length === 0) {
7418
- return;
7419
- }
7420
- for (const k of Object.keys(blockTokens)) {
7421
- included.add(k);
7422
- }
7423
- blocks.push({ key, description, tokens: blockTokens });
7424
- };
7425
7423
  for (const item of resolver.resolutionOrder) {
7426
7424
  const ref = item.$ref;
7427
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
7425
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
7428
7426
  continue;
7429
7427
  }
7430
- const setName = ref.slice("#/sets/".length);
7431
- addBlock(
7428
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7429
+ addLayerBlock(
7430
+ blocks,
7431
+ included,
7432
7432
  `Set: ${setName}`,
7433
7433
  collectSetTokens(tokens, setName, included),
7434
7434
  resolver.sets?.[setName]?.description
7435
7435
  );
7436
7436
  }
7437
- addBlock("Unattributed", collectRemainder(tokens, included));
7437
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7438
7438
  return blocks;
7439
7439
  }
7440
7440
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
7441
7441
  const blocks = [];
7442
7442
  const included = /* @__PURE__ */ new Set();
7443
7443
  const baseInputs = normalizeModifierInputs(baseModifierInputs);
7444
- const addBlock = (key, blockTokens, description) => {
7445
- if (Object.keys(blockTokens).length === 0) {
7446
- return;
7447
- }
7448
- for (const k of Object.keys(blockTokens)) {
7449
- included.add(k);
7450
- }
7451
- blocks.push({ key, description, tokens: blockTokens });
7452
- };
7453
7444
  for (const item of resolver.resolutionOrder) {
7454
7445
  const ref = item.$ref;
7455
7446
  if (typeof ref !== "string") {
7456
7447
  continue;
7457
7448
  }
7458
- if (ref.startsWith("#/sets/")) {
7459
- const setName = ref.slice("#/sets/".length);
7460
- addBlock(
7461
- `Set: ${setName}`,
7462
- collectSetTokens(tokens, setName, included),
7463
- resolver.sets?.[setName]?.description
7464
- );
7465
- continue;
7466
- }
7467
- if (ref.startsWith("#/modifiers/")) {
7468
- const modifierName = ref.slice("#/modifiers/".length);
7469
- const modifier = resolver.modifiers?.[modifierName];
7470
- const selectedContext = baseInputs[modifierName.toLowerCase()];
7471
- if (!modifier || !selectedContext) {
7472
- continue;
7473
- }
7474
- const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7475
- addBlock(
7476
- `Modifier: ${modifierName}=${selectedContext} (default)`,
7477
- collectModifierTokens(tokens, expectedSource, included),
7478
- modifier.description
7479
- );
7480
- }
7449
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
7481
7450
  }
7482
- addBlock("Unattributed", collectRemainder(tokens, included));
7451
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7483
7452
  return blocks;
7484
7453
  }
7454
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
7455
+ if (ref.startsWith(REF_PREFIX_SETS)) {
7456
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7457
+ addLayerBlock(
7458
+ blocks,
7459
+ included,
7460
+ `Set: ${setName}`,
7461
+ collectSetTokens(tokens, setName, included),
7462
+ resolver.sets?.[setName]?.description
7463
+ );
7464
+ return;
7465
+ }
7466
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7467
+ return;
7468
+ }
7469
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
7470
+ const modifier = resolver.modifiers?.[modifierName];
7471
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
7472
+ if (!modifier || !selectedContext) {
7473
+ return;
7474
+ }
7475
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7476
+ addLayerBlock(
7477
+ blocks,
7478
+ included,
7479
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
7480
+ collectModifierTokens(tokens, expectedSource, included),
7481
+ modifier.description
7482
+ );
7483
+ }
7485
7484
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
7486
7485
  const normalizedModifier = modifierName.toLowerCase();
7487
7486
  const normalizedContext = context.toLowerCase();
@@ -7496,35 +7495,19 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
7496
7495
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
7497
7496
  });
7498
7497
  }
7499
- function orderBundleData(bundleData, resolver, baseItem) {
7500
- const modifiers = resolver.modifiers;
7501
- if (!modifiers) {
7502
- return bundleData;
7498
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
7499
+ if (!item) {
7500
+ return;
7503
7501
  }
7504
- const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7505
- const orderedModifierNames = getOrderedModifierNames(resolver);
7506
- if (orderedModifierNames.length === 0) {
7507
- return bundleData;
7502
+ const key = stableInputsKey(item.modifierInputs);
7503
+ if (includedKeys.has(key)) {
7504
+ return;
7508
7505
  }
7509
- const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7510
- if (!firstModifierDef) {
7511
- return bundleData;
7512
- }
7513
- const includedKeys = /* @__PURE__ */ new Set();
7514
- const ordered = [];
7515
- const pushUnique = (item) => {
7516
- if (!item) {
7517
- return;
7518
- }
7519
- const key = stableInputsKey(item.modifierInputs);
7520
- if (includedKeys.has(key)) {
7521
- return;
7522
- }
7523
- includedKeys.add(key);
7524
- ordered.push(item);
7525
- };
7526
- pushUnique(baseItem);
7527
- for (const modifierName of orderedModifierNames) {
7506
+ includedKeys.add(key);
7507
+ ordered.push(item);
7508
+ }
7509
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
7510
+ for (const modifierName of orderedNames) {
7528
7511
  const modifierDef = modifiers[modifierName];
7529
7512
  if (!modifierDef) {
7530
7513
  continue;
@@ -7534,9 +7517,39 @@ function orderBundleData(bundleData, resolver, baseItem) {
7534
7517
  if (defaultValue === ctx.toLowerCase()) {
7535
7518
  continue;
7536
7519
  }
7537
- pushUnique(findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs));
7520
+ pushUniqueBundleItem(
7521
+ ordered,
7522
+ includedKeys,
7523
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
7524
+ );
7538
7525
  }
7539
7526
  }
7527
+ }
7528
+ function orderBundleData(bundleData, resolver, baseItem) {
7529
+ const modifiers = resolver.modifiers;
7530
+ if (!modifiers) {
7531
+ return bundleData;
7532
+ }
7533
+ const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7534
+ const orderedModifierNames = getOrderedModifierNames(resolver);
7535
+ if (orderedModifierNames.length === 0) {
7536
+ return bundleData;
7537
+ }
7538
+ const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7539
+ if (!firstModifierDef) {
7540
+ return bundleData;
7541
+ }
7542
+ const includedKeys = /* @__PURE__ */ new Set();
7543
+ const ordered = [];
7544
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
7545
+ appendModifierPermutations(
7546
+ bundleData,
7547
+ modifiers,
7548
+ orderedModifierNames,
7549
+ baseInputs,
7550
+ ordered,
7551
+ includedKeys
7552
+ );
7540
7553
  return ordered.length > 0 ? ordered : bundleData;
7541
7554
  }
7542
7555
  function getOrderedModifierNames(resolver) {
@@ -7548,10 +7561,10 @@ function getOrderedModifierNames(resolver) {
7548
7561
  if (typeof ref !== "string") {
7549
7562
  continue;
7550
7563
  }
7551
- if (!ref.startsWith("#/modifiers/")) {
7564
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7552
7565
  continue;
7553
7566
  }
7554
- const name = ref.slice("#/modifiers/".length);
7567
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
7555
7568
  if (seen.has(name)) {
7556
7569
  continue;
7557
7570
  }
@@ -7622,24 +7635,22 @@ var CssRenderer = class _CssRenderer {
7622
7635
  ...options,
7623
7636
  referenceTokens: options?.referenceTokens ?? tokens
7624
7637
  };
7625
- const groups = this.groupTokens(tokens, opts);
7638
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7626
7639
  const referenceTokens = opts.referenceTokens;
7627
7640
  const lines = [];
7628
- for (const [selector, groupTokens] of Object.entries(groups)) {
7629
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
7630
- }
7641
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
7631
7642
  const cssString = lines.join("");
7632
7643
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
7633
7644
  }
7634
7645
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
7635
- const indent2 = opts.minify ? "" : " ";
7646
+ const indent = opts.minify ? "" : " ";
7636
7647
  const newline = opts.minify ? "" : "\n";
7637
7648
  const space = opts.minify ? "" : " ";
7638
7649
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
7639
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
7650
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
7640
7651
  if (hasMediaQuery) {
7641
7652
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
7642
- lines.push(`${indent2}${selector}${space}{${newline}`);
7653
+ lines.push(`${indent}${selector}${space}{${newline}`);
7643
7654
  } else {
7644
7655
  lines.push(`${selector}${space}{${newline}`);
7645
7656
  }
@@ -7656,21 +7667,21 @@ var CssRenderer = class _CssRenderer {
7656
7667
  );
7657
7668
  }
7658
7669
  if (hasMediaQuery) {
7659
- lines.push(`${indent2}}${newline}`);
7670
+ lines.push(`${indent}}${newline}`);
7660
7671
  }
7661
7672
  lines.push(`}${newline}${newline}`);
7662
7673
  }
7663
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
7674
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
7664
7675
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
7665
7676
  if (token.$deprecated != null && token.$deprecated !== false) {
7666
7677
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
7667
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7678
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7668
7679
  }
7669
7680
  if (token.$description && token.$description !== "") {
7670
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7681
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7671
7682
  }
7672
7683
  for (const entry of entries) {
7673
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
7684
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
7674
7685
  }
7675
7686
  }
7676
7687
  async formatWithPrettier(css2) {
@@ -7685,15 +7696,6 @@ var CssRenderer = class _CssRenderer {
7685
7696
  return css2;
7686
7697
  }
7687
7698
  }
7688
- /**
7689
- * Group tokens by selector (for theme support)
7690
- */
7691
- groupTokens(tokens, options) {
7692
- const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7693
- return {
7694
- [options.selector]: sortedTokens
7695
- };
7696
- }
7697
7699
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
7698
7700
  if (preserveReferences) {
7699
7701
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -7835,7 +7837,7 @@ var CssRenderer = class _CssRenderer {
7835
7837
  leaves.push({ path: path7, value });
7836
7838
  return;
7837
7839
  }
7838
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
7840
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
7839
7841
  leaves.push({ path: path7, value });
7840
7842
  return;
7841
7843
  }
@@ -7878,8 +7880,8 @@ var CssRenderer = class _CssRenderer {
7878
7880
  if (isDimensionObject(value)) {
7879
7881
  return dimensionObjectToString(value);
7880
7882
  }
7881
- if (this.isDurationObject(value)) {
7882
- return this.formatDurationValue(value);
7883
+ if (isDurationObject(value)) {
7884
+ return durationObjectToString(value);
7883
7885
  }
7884
7886
  if (typeof value === "string") {
7885
7887
  return value;
@@ -7942,15 +7944,6 @@ var CssRenderer = class _CssRenderer {
7942
7944
  isPrimitiveValue(value) {
7943
7945
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
7944
7946
  }
7945
- isDurationObject(value) {
7946
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
7947
- }
7948
- formatDurationValue(value) {
7949
- if (typeof value === "string") {
7950
- return value;
7951
- }
7952
- return `${value.value}${value.unit}`;
7953
- }
7954
7947
  /**
7955
7948
  * Format token value for CSS
7956
7949
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -7971,8 +7964,8 @@ var CssRenderer = class _CssRenderer {
7971
7964
  return typeof value === "string" ? value : dimensionObjectToString(value);
7972
7965
  }
7973
7966
  if (type === "duration") {
7974
- if (this.isDurationObject(value)) {
7975
- return this.formatDurationValue(value);
7967
+ if (isDurationObject(value)) {
7968
+ return durationObjectToString(value);
7976
7969
  }
7977
7970
  if (typeof value === "string") {
7978
7971
  return value;
@@ -8062,16 +8055,16 @@ var CssRenderer = class _CssRenderer {
8062
8055
  */
8063
8056
  formatTransition(value) {
8064
8057
  const parts = [];
8065
- if (this.isDurationObject(value.duration)) {
8066
- parts.push(this.formatDurationValue(value.duration));
8058
+ if (isDurationObject(value.duration)) {
8059
+ parts.push(durationObjectToString(value.duration));
8067
8060
  } else if (value.duration != null) {
8068
8061
  parts.push(String(value.duration));
8069
8062
  }
8070
8063
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
8071
8064
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
8072
8065
  }
8073
- if (this.isDurationObject(value.delay)) {
8074
- parts.push(this.formatDurationValue(value.delay));
8066
+ if (isDurationObject(value.delay)) {
8067
+ parts.push(durationObjectToString(value.delay));
8075
8068
  } else if (value.delay != null) {
8076
8069
  parts.push(String(value.delay));
8077
8070
  }
@@ -8081,7 +8074,7 @@ var CssRenderer = class _CssRenderer {
8081
8074
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8082
8075
  tokens,
8083
8076
  modifierInputs,
8084
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8077
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8085
8078
  }));
8086
8079
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
8087
8080
  return await this.formatTokens(tokens, {
@@ -8091,12 +8084,12 @@ var CssRenderer = class _CssRenderer {
8091
8084
  });
8092
8085
  }
8093
8086
  async formatStandalone(context, options) {
8094
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8095
- if (!context.output.file && requiresFile) {
8096
- throw new ConfigurationError(
8097
- `Output "${context.output.name}": file is required for standalone CSS output`
8098
- );
8099
- }
8087
+ assertFileRequired(
8088
+ context.buildPath,
8089
+ context.output.file,
8090
+ context.output.name,
8091
+ "standalone CSS"
8092
+ );
8100
8093
  const files = {};
8101
8094
  for (const { tokens, modifierInputs } of context.permutations) {
8102
8095
  const { fileName, content } = await this.buildStandaloneFile(
@@ -8110,7 +8103,7 @@ var CssRenderer = class _CssRenderer {
8110
8103
  return { kind: "outputTree", files };
8111
8104
  }
8112
8105
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
8113
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
8106
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
8114
8107
  const { modifierName, modifierContext } = this.resolveModifierContext(
8115
8108
  modifierInputs,
8116
8109
  context,
@@ -8147,12 +8140,7 @@ var CssRenderer = class _CssRenderer {
8147
8140
  return { fileName, content };
8148
8141
  }
8149
8142
  async formatModifier(context, options) {
8150
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8151
- if (!context.output.file && requiresFile) {
8152
- throw new ConfigurationError(
8153
- `Output "${context.output.name}": file is required for modifier CSS output`
8154
- );
8155
- }
8143
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
8156
8144
  if (!context.resolver.modifiers) {
8157
8145
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
8158
8146
  }
@@ -8178,7 +8166,7 @@ var CssRenderer = class _CssRenderer {
8178
8166
  }
8179
8167
  async buildModifierBaseFile(context, options) {
8180
8168
  const basePermutation = context.permutations.find(
8181
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
8169
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
8182
8170
  );
8183
8171
  if (!basePermutation) {
8184
8172
  return void 0;
@@ -8191,25 +8179,40 @@ var CssRenderer = class _CssRenderer {
8191
8179
  if (setBlocks.length === 0) {
8192
8180
  return void 0;
8193
8181
  }
8182
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
8183
+ const content = await this.formatSetBlocksCss(
8184
+ setBlocks,
8185
+ basePermutation.tokens,
8186
+ selector,
8187
+ mediaQuery,
8188
+ options
8189
+ );
8190
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8191
+ return { fileName, content };
8192
+ }
8193
+ resolveBaseModifierContext(context, options) {
8194
8194
  const modifiers = context.resolver.modifiers;
8195
8195
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
8196
8196
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
8197
8197
  const baseModifierInputs = { ...context.meta.defaults };
8198
- const selector = resolveSelector(
8199
- options.selector,
8200
- firstModifierName,
8201
- firstModifierContext,
8202
- true,
8203
- baseModifierInputs
8204
- );
8205
- const mediaQuery = resolveMediaQuery(
8206
- options.mediaQuery,
8207
- firstModifierName,
8208
- firstModifierContext,
8209
- true,
8210
- baseModifierInputs
8211
- );
8212
- const referenceTokens = basePermutation.tokens;
8198
+ return {
8199
+ selector: resolveSelector(
8200
+ options.selector,
8201
+ firstModifierName,
8202
+ firstModifierContext,
8203
+ true,
8204
+ baseModifierInputs
8205
+ ),
8206
+ mediaQuery: resolveMediaQuery(
8207
+ options.mediaQuery,
8208
+ firstModifierName,
8209
+ firstModifierContext,
8210
+ true,
8211
+ baseModifierInputs
8212
+ )
8213
+ };
8214
+ }
8215
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
8213
8216
  const cssBlocks = [];
8214
8217
  for (const block of setBlocks) {
8215
8218
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -8225,9 +8228,7 @@ var CssRenderer = class _CssRenderer {
8225
8228
  cssBlocks.push(`${header}
8226
8229
  ${css2}`);
8227
8230
  }
8228
- const content = cssBlocks.join("\n");
8229
- const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8230
- return { fileName, content };
8231
+ return cssBlocks.join("\n");
8231
8232
  }
8232
8233
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
8233
8234
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -8304,13 +8305,6 @@ ${css2}`);
8304
8305
  }
8305
8306
  return { modifierName: "", modifierContext: "" };
8306
8307
  }
8307
- isBasePermutation(modifierInputs, defaults) {
8308
- const normalizedInputs = normalizeModifierInputs(modifierInputs);
8309
- const normalizedDefaults = normalizeModifierInputs(defaults);
8310
- return Object.entries(normalizedDefaults).every(
8311
- ([key, value]) => normalizedInputs[key] === value
8312
- );
8313
- }
8314
8308
  };
8315
8309
  function cssRenderer() {
8316
8310
  const rendererInstance = new CssRenderer();
@@ -8323,8 +8317,6 @@ function cssRenderer() {
8323
8317
  }
8324
8318
 
8325
8319
  // src/renderers/ios.ts
8326
- init_errors();
8327
- init_token_utils();
8328
8320
  init_utils();
8329
8321
  var toSRGB2 = converter("rgb");
8330
8322
  var toP32 = converter("p3");
@@ -8413,94 +8405,68 @@ var IosRenderer = class {
8413
8405
  return await this.formatStandalone(context, opts);
8414
8406
  }
8415
8407
  formatTokens(tokens, options) {
8416
- if (options.structure === "grouped") {
8417
- return this.formatAsGrouped(tokens, options);
8418
- }
8419
- return this.formatAsEnum(tokens, options);
8420
- }
8421
- formatAsEnum(tokens, options) {
8422
8408
  const access3 = options.accessLevel;
8423
- const groups = this.groupTokensByType(tokens);
8409
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
8424
8410
  const imports = this.collectImports(tokens);
8425
- const i1 = this.indentStr(options.indent, 1);
8426
- const i2 = this.indentStr(options.indent, 2);
8427
8411
  const staticPrefix = this.staticLetPrefix(options);
8428
8412
  const frozen = this.frozenPrefix(options);
8429
8413
  const lines = [];
8430
- lines.push(this.buildFileHeader());
8414
+ lines.push(buildGeneratedFileHeader());
8431
8415
  lines.push("");
8432
8416
  for (const imp of imports) {
8433
8417
  lines.push(`import ${imp}`);
8434
8418
  }
8435
8419
  lines.push(...this.buildStructDefinitions(tokens, access3, options));
8420
+ this.pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen);
8421
+ lines.push(...this.buildViewExtensions(tokens, access3, options));
8422
+ if (options.structure !== "grouped") {
8423
+ lines.push("");
8424
+ }
8425
+ return lines.join("\n");
8426
+ }
8427
+ pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen) {
8428
+ const i1 = indentStr(options.indent, 1);
8429
+ const i2 = indentStr(options.indent, 2);
8430
+ if (options.structure === "grouped") {
8431
+ this.pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen);
8432
+ return;
8433
+ }
8436
8434
  lines.push("");
8437
8435
  lines.push(`${frozen}${access3} enum ${options.enumName} {`);
8438
8436
  for (const group of groups) {
8439
8437
  lines.push(`${i1}${frozen}${access3} enum ${group.name} {`);
8440
- for (const token of group.tokens) {
8441
- const swiftName = this.buildQualifiedSwiftName(token);
8442
- const swiftValue = this.formatSwiftValue(token, options);
8443
- const typeAnnotation = this.getTypeAnnotation(token);
8444
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8445
- const docComment = this.buildDocComment(token, i2);
8446
- if (docComment) {
8447
- lines.push(docComment);
8448
- }
8449
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8450
- }
8438
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8451
8439
  lines.push(`${i1}}`);
8452
8440
  lines.push("");
8453
8441
  }
8454
8442
  lines.push("}");
8455
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8456
- lines.push("");
8457
- return lines.join("\n");
8458
8443
  }
8459
- formatAsGrouped(tokens, options) {
8460
- const access3 = options.accessLevel;
8444
+ pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen) {
8461
8445
  const namespace = options.extensionNamespace;
8462
- const groups = this.groupTokensByType(tokens);
8463
- const imports = this.collectImports(tokens);
8464
- const i1 = this.indentStr(options.indent, 1);
8465
- const i2 = this.indentStr(options.indent, 2);
8466
- const staticPrefix = this.staticLetPrefix(options);
8467
- const frozen = this.frozenPrefix(options);
8468
- const lines = [];
8469
- lines.push(this.buildFileHeader());
8470
- lines.push("");
8471
- for (const imp of imports) {
8472
- lines.push(`import ${imp}`);
8473
- }
8474
- lines.push(...this.buildStructDefinitions(tokens, access3, options));
8475
8446
  lines.push("");
8476
8447
  lines.push(`${frozen}${access3} enum ${namespace} {}`);
8477
8448
  lines.push("");
8478
8449
  for (const group of groups) {
8479
8450
  lines.push(`${access3} extension ${namespace} {`);
8480
8451
  lines.push(`${i1}${frozen}enum ${group.name} {`);
8481
- for (const token of group.tokens) {
8482
- const swiftName = this.buildQualifiedSwiftName(token);
8483
- const swiftValue = this.formatSwiftValue(token, options);
8484
- const typeAnnotation = this.getTypeAnnotation(token);
8485
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8486
- const docComment = this.buildDocComment(token, i2);
8487
- if (docComment) {
8488
- lines.push(docComment);
8489
- }
8490
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8491
- }
8452
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8492
8453
  lines.push(`${i1}}`);
8493
8454
  lines.push("}");
8494
8455
  lines.push("");
8495
8456
  }
8496
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8497
- return lines.join("\n");
8498
8457
  }
8499
- buildFileHeader() {
8500
- return [
8501
- "// Generated by Dispersa - do not edit manually",
8502
- "// https://github.com/timges/dispersa"
8503
- ].join("\n");
8458
+ pushTokenDeclarations(lines, tokens, options, access3, indent, staticPrefix) {
8459
+ for (const token of tokens) {
8460
+ const swiftName = this.buildQualifiedSwiftName(token);
8461
+ const swiftValue = this.formatSwiftValue(token, options);
8462
+ const typeAnnotation = this.getTypeAnnotation(token);
8463
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8464
+ const docComment = this.buildDocComment(token, indent);
8465
+ if (docComment) {
8466
+ lines.push(docComment);
8467
+ }
8468
+ lines.push(`${indent}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8469
+ }
8504
8470
  }
8505
8471
  collectImports(tokens) {
8506
8472
  const imports = /* @__PURE__ */ new Set();
@@ -8515,24 +8481,11 @@ var IosRenderer = class {
8515
8481
  /**
8516
8482
  * Builds a `///` doc comment from a token's `$description`, if present.
8517
8483
  */
8518
- buildDocComment(token, indent2) {
8484
+ buildDocComment(token, indent) {
8519
8485
  if (!token.$description) {
8520
8486
  return void 0;
8521
8487
  }
8522
- return `${indent2}/// ${token.$description}`;
8523
- }
8524
- groupTokensByType(tokens) {
8525
- const groupMap = /* @__PURE__ */ new Map();
8526
- for (const [, token] of getSortedTokenEntries(tokens)) {
8527
- const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
8528
- const existing = groupMap.get(groupName) ?? [];
8529
- existing.push(token);
8530
- groupMap.set(groupName, existing);
8531
- }
8532
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
8533
- name,
8534
- tokens: groupTokens
8535
- }));
8488
+ return `${indent}/// ${token.$description}`;
8536
8489
  }
8537
8490
  /**
8538
8491
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -8545,43 +8498,40 @@ var IosRenderer = class {
8545
8498
  const path7 = token.path;
8546
8499
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
8547
8500
  const joined = withoutTypePrefix.join("_");
8548
- return this.toSwiftIdentifier(joined);
8501
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
8549
8502
  }
8550
8503
  formatSwiftValue(token, options) {
8551
- const value = token.$value;
8552
- if (token.$type === "color") {
8553
- return this.formatColorValue(value, options);
8554
- }
8555
- if (token.$type === "dimension") {
8556
- return this.formatDimensionValue(value);
8557
- }
8558
- if (token.$type === "fontFamily") {
8559
- return this.formatFontFamilyValue(value);
8560
- }
8561
- if (token.$type === "fontWeight") {
8562
- return this.formatFontWeightValue(value);
8563
- }
8564
- if (token.$type === "duration") {
8565
- return this.formatDurationValue(value);
8566
- }
8567
- if (token.$type === "shadow") {
8568
- return this.formatShadowValue(value, options);
8569
- }
8570
- if (token.$type === "typography") {
8571
- return this.formatTypographyValue(value);
8572
- }
8573
- if (token.$type === "border") {
8574
- return this.formatBorderValue(value, options);
8575
- }
8576
- if (token.$type === "gradient") {
8577
- return this.formatGradientValue(value, options);
8578
- }
8579
- if (token.$type === "number") {
8580
- return String(value);
8581
- }
8582
- if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
8583
- return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8504
+ const { $type, $value: value } = token;
8505
+ switch ($type) {
8506
+ case "color":
8507
+ return this.formatColorValue(value, options);
8508
+ case "dimension":
8509
+ return this.formatDimensionValue(value);
8510
+ case "fontFamily":
8511
+ return this.formatFontFamilyValue(value);
8512
+ case "fontWeight":
8513
+ return this.formatFontWeightValue(value);
8514
+ case "duration":
8515
+ return this.formatDurationValue(value);
8516
+ case "shadow":
8517
+ return this.formatShadowValue(value, options);
8518
+ case "typography":
8519
+ return this.formatTypographyValue(value);
8520
+ case "border":
8521
+ return this.formatBorderValue(value, options);
8522
+ case "gradient":
8523
+ return this.formatGradientValue(value, options);
8524
+ case "number":
8525
+ return String(value);
8526
+ case "cubicBezier":
8527
+ if (Array.isArray(value) && value.length === 4) {
8528
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8529
+ }
8530
+ break;
8584
8531
  }
8532
+ return this.formatSwiftPrimitive(value);
8533
+ }
8534
+ formatSwiftPrimitive(value) {
8585
8535
  if (typeof value === "string") {
8586
8536
  return `"${this.escapeSwiftString(value)}"`;
8587
8537
  }
@@ -8614,9 +8564,7 @@ var IosRenderer = class {
8614
8564
  }
8615
8565
  formatDimensionValue(value) {
8616
8566
  if (isDimensionObject(value)) {
8617
- const dim = value;
8618
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8619
- return String(ptValue);
8567
+ return this.dimensionToPoints(value);
8620
8568
  }
8621
8569
  return String(value);
8622
8570
  }
@@ -8683,7 +8631,7 @@ var IosRenderer = class {
8683
8631
  return map[name.toLowerCase()];
8684
8632
  }
8685
8633
  formatDurationValue(value) {
8686
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
8634
+ if (isDurationObject(value)) {
8687
8635
  const dur = value;
8688
8636
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
8689
8637
  return String(seconds);
@@ -8732,9 +8680,7 @@ var IosRenderer = class {
8732
8680
  if (!isDimensionObject(typo.letterSpacing)) {
8733
8681
  return "0";
8734
8682
  }
8735
- const dim = typo.letterSpacing;
8736
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8737
- return String(ptValue);
8683
+ return this.dimensionToPoints(typo.letterSpacing);
8738
8684
  }
8739
8685
  extractLineSpacing(typo) {
8740
8686
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -8743,18 +8689,19 @@ var IosRenderer = class {
8743
8689
  if (!isDimensionObject(typo.fontSize)) {
8744
8690
  return "0";
8745
8691
  }
8746
- const dim = typo.fontSize;
8747
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
8692
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
8748
8693
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
8749
8694
  return String(lineHeightPt - basePt);
8750
8695
  }
8696
+ dimensionToNumericPoints(dim) {
8697
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
8698
+ }
8751
8699
  dimensionToPoints(dim) {
8752
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8753
- return String(ptValue);
8700
+ return String(this.dimensionToNumericPoints(dim));
8754
8701
  }
8755
8702
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
8756
8703
  dimensionToCGFloat(dim) {
8757
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8704
+ const ptValue = this.dimensionToNumericPoints(dim);
8758
8705
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
8759
8706
  }
8760
8707
  getTypeAnnotation(token) {
@@ -8773,21 +8720,12 @@ var IosRenderer = class {
8773
8720
  return void 0;
8774
8721
  }
8775
8722
  }
8776
- toSwiftIdentifier(name) {
8777
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
8778
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
8779
- const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
8780
- return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
8781
- }
8782
8723
  escapeSwiftString(str) {
8783
8724
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
8784
8725
  }
8785
8726
  roundComponent(value) {
8786
8727
  return Math.round(value * 1e4) / 1e4;
8787
8728
  }
8788
- indentStr(width, level) {
8789
- return " ".repeat(width * level);
8790
- }
8791
8729
  /**
8792
8730
  * Returns the prefix for `static let` declarations.
8793
8731
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -8803,34 +8741,25 @@ var IosRenderer = class {
8803
8741
  structConformances(options) {
8804
8742
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
8805
8743
  }
8806
- hasShadowTokens(tokens) {
8807
- return Object.values(tokens).some((t) => t.$type === "shadow");
8808
- }
8809
- hasTypographyTokens(tokens) {
8810
- return Object.values(tokens).some((t) => t.$type === "typography");
8811
- }
8812
- hasBorderTokens(tokens) {
8813
- return Object.values(tokens).some((t) => t.$type === "border");
8814
- }
8815
8744
  /** Emits all struct definitions needed by the token set. */
8816
8745
  buildStructDefinitions(tokens, access3, options) {
8817
8746
  const lines = [];
8818
- if (this.hasShadowTokens(tokens)) {
8747
+ if (Object.values(tokens).some(isShadowToken)) {
8819
8748
  lines.push("");
8820
8749
  lines.push(...this.buildShadowStyleStruct(access3, options));
8821
8750
  }
8822
- if (this.hasTypographyTokens(tokens)) {
8751
+ if (Object.values(tokens).some(isTypographyToken)) {
8823
8752
  lines.push("");
8824
8753
  lines.push(...this.buildTypographyStyleStruct(access3, options));
8825
8754
  }
8826
- if (this.hasBorderTokens(tokens)) {
8755
+ if (Object.values(tokens).some(isBorderToken)) {
8827
8756
  lines.push("");
8828
8757
  lines.push(...this.buildBorderStyleStruct(access3, options));
8829
8758
  }
8830
8759
  return lines;
8831
8760
  }
8832
8761
  buildShadowStyleStruct(access3, options) {
8833
- const i1 = this.indentStr(options.indent, 1);
8762
+ const i1 = indentStr(options.indent, 1);
8834
8763
  const conformances = this.structConformances(options);
8835
8764
  const frozen = this.frozenPrefix(options);
8836
8765
  return [
@@ -8844,7 +8773,7 @@ var IosRenderer = class {
8844
8773
  ];
8845
8774
  }
8846
8775
  buildTypographyStyleStruct(access3, options) {
8847
- const i1 = this.indentStr(options.indent, 1);
8776
+ const i1 = indentStr(options.indent, 1);
8848
8777
  const conformances = this.structConformances(options);
8849
8778
  const frozen = this.frozenPrefix(options);
8850
8779
  return [
@@ -8856,7 +8785,7 @@ var IosRenderer = class {
8856
8785
  ];
8857
8786
  }
8858
8787
  buildBorderStyleStruct(access3, options) {
8859
- const i1 = this.indentStr(options.indent, 1);
8788
+ const i1 = indentStr(options.indent, 1);
8860
8789
  const conformances = this.structConformances(options);
8861
8790
  const frozen = this.frozenPrefix(options);
8862
8791
  return [
@@ -8869,9 +8798,9 @@ var IosRenderer = class {
8869
8798
  /** Emits convenience View extensions for shadow and typography application. */
8870
8799
  buildViewExtensions(tokens, access3, options) {
8871
8800
  const lines = [];
8872
- const i1 = this.indentStr(options.indent, 1);
8873
- const i2 = this.indentStr(options.indent, 2);
8874
- if (this.hasShadowTokens(tokens)) {
8801
+ const i1 = indentStr(options.indent, 1);
8802
+ const i2 = indentStr(options.indent, 2);
8803
+ if (Object.values(tokens).some(isShadowToken)) {
8875
8804
  lines.push("");
8876
8805
  lines.push(`${access3} extension View {`);
8877
8806
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -8881,7 +8810,7 @@ var IosRenderer = class {
8881
8810
  lines.push(`${i1}}`);
8882
8811
  lines.push("}");
8883
8812
  }
8884
- if (this.hasTypographyTokens(tokens)) {
8813
+ if (Object.values(tokens).some(isTypographyToken)) {
8885
8814
  lines.push("");
8886
8815
  lines.push(`${access3} extension View {`);
8887
8816
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -8913,12 +8842,12 @@ var IosRenderer = class {
8913
8842
  return `Gradient(stops: [${stops.join(", ")}])`;
8914
8843
  }
8915
8844
  async formatStandalone(context, options) {
8916
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8917
- if (!context.output.file && requiresFile) {
8918
- throw new ConfigurationError(
8919
- `Output "${context.output.name}": file is required for standalone iOS output`
8920
- );
8921
- }
8845
+ assertFileRequired(
8846
+ context.buildPath,
8847
+ context.output.file,
8848
+ context.output.name,
8849
+ "standalone iOS"
8850
+ );
8922
8851
  const files = {};
8923
8852
  for (const { tokens, modifierInputs } of context.permutations) {
8924
8853
  const processedTokens = stripInternalMetadata(tokens);
@@ -8947,7 +8876,6 @@ function iosRenderer() {
8947
8876
 
8948
8877
  // src/renderers/js-module.ts
8949
8878
  init_utils();
8950
- init_errors();
8951
8879
  init_token_utils();
8952
8880
  var JsModuleRenderer = class {
8953
8881
  async format(context, options) {
@@ -8963,18 +8891,13 @@ var JsModuleRenderer = class {
8963
8891
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8964
8892
  tokens: stripInternalMetadata(tokens),
8965
8893
  modifierInputs,
8966
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8894
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8967
8895
  }));
8968
8896
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
8969
8897
  return await this.formatTokens(tokens, opts);
8970
8898
  });
8971
8899
  }
8972
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8973
- if (!context.output.file && requiresFile) {
8974
- throw new ConfigurationError(
8975
- `Output "${context.output.name}": file is required for JS module output`
8976
- );
8977
- }
8900
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
8978
8901
  const files = {};
8979
8902
  for (const { tokens, modifierInputs } of context.permutations) {
8980
8903
  const cleanTokens = stripInternalMetadata(tokens);
@@ -9028,42 +8951,18 @@ var JsModuleRenderer = class {
9028
8951
  lines.push(`export default ${varName}`);
9029
8952
  return lines;
9030
8953
  }
9031
- /**
9032
- * Convert tokens to plain object with flat or nested structure
9033
- */
9034
8954
  tokensToPlainObject(tokens, structure) {
8955
+ if (structure === "nested") {
8956
+ return buildNestedTokenObject(tokens, (token) => token.$value);
8957
+ }
9035
8958
  const result = {};
9036
- if (structure === "flat") {
9037
- for (const [name, token] of getSortedTokenEntries(tokens)) {
9038
- result[name] = token.$value;
9039
- }
9040
- } else {
9041
- for (const [, token] of getSortedTokenEntries(tokens)) {
9042
- const parts = token.path;
9043
- let current = result;
9044
- for (let i = 0; i < parts.length - 1; i++) {
9045
- const part = parts[i];
9046
- if (part == null) {
9047
- continue;
9048
- }
9049
- if (!(part in current)) {
9050
- current[part] = {};
9051
- }
9052
- current = current[part];
9053
- }
9054
- const lastPart = parts[parts.length - 1];
9055
- if (lastPart != null) {
9056
- current[lastPart] = token.$value;
9057
- }
9058
- }
8959
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
8960
+ result[name] = token.$value;
9059
8961
  }
9060
8962
  return result;
9061
8963
  }
9062
- /**
9063
- * Add object properties to lines
9064
- */
9065
- addObjectProperties(lines, obj, indent2) {
9066
- const indentStr = " ".repeat(indent2);
8964
+ addObjectProperties(lines, obj, indent) {
8965
+ const indentStr2 = " ".repeat(indent);
9067
8966
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
9068
8967
  for (let i = 0; i < entries.length; i++) {
9069
8968
  const entry = entries[i];
@@ -9072,14 +8971,16 @@ var JsModuleRenderer = class {
9072
8971
  }
9073
8972
  const [key, value] = entry;
9074
8973
  const isLast = i === entries.length - 1;
9075
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
9076
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
9077
- this.addObjectProperties(lines, value, indent2 + 1);
9078
- lines.push(`${indentStr}}${isLast ? "" : ","}`);
9079
- } else {
9080
- const valueStr = JSON.stringify(value);
9081
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueStr}${isLast ? "" : ","}`);
8974
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
8975
+ if (!isNestedObject) {
8976
+ lines.push(
8977
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
8978
+ );
8979
+ continue;
9082
8980
  }
8981
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
8982
+ this.addObjectProperties(lines, value, indent + 1);
8983
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
9083
8984
  }
9084
8985
  }
9085
8986
  /**
@@ -9091,9 +8992,6 @@ var JsModuleRenderer = class {
9091
8992
  }
9092
8993
  return `"${key}"`;
9093
8994
  }
9094
- isBasePermutation(modifierInputs, defaults) {
9095
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9096
- }
9097
8995
  };
9098
8996
  function jsRenderer() {
9099
8997
  const rendererInstance = new JsModuleRenderer();
@@ -9107,7 +9005,6 @@ function jsRenderer() {
9107
9005
 
9108
9006
  // src/renderers/json.ts
9109
9007
  init_utils();
9110
- init_errors();
9111
9008
  init_token_utils();
9112
9009
  var JsonRenderer = class {
9113
9010
  async format(context, options) {
@@ -9122,18 +9019,13 @@ var JsonRenderer = class {
9122
9019
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9123
9020
  tokens: stripInternalMetadata(tokens),
9124
9021
  modifierInputs,
9125
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9022
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9126
9023
  }));
9127
9024
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
9128
9025
  return await this.formatTokens(tokens, opts);
9129
9026
  });
9130
9027
  }
9131
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9132
- if (!context.output.file && requiresFile) {
9133
- throw new ConfigurationError(
9134
- `Output "${context.output.name}": file is required for JSON output`
9135
- );
9136
- }
9028
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
9137
9029
  const files = {};
9138
9030
  for (const { tokens, modifierInputs } of context.permutations) {
9139
9031
  const processedTokens = stripInternalMetadata(tokens);
@@ -9193,55 +9085,11 @@ var JsonRenderer = class {
9193
9085
  }
9194
9086
  return result;
9195
9087
  }
9196
- /**
9197
- * Nest tokens by path (values only)
9198
- */
9199
9088
  nestValues(tokens) {
9200
- const result = {};
9201
- for (const [, token] of getSortedTokenEntries(tokens)) {
9202
- const parts = token.path;
9203
- let current = result;
9204
- for (let i = 0; i < parts.length - 1; i++) {
9205
- const part = parts[i];
9206
- if (part === null || part === void 0) {
9207
- continue;
9208
- }
9209
- if (!(part in current)) {
9210
- current[part] = {};
9211
- }
9212
- current = current[part];
9213
- }
9214
- const lastPart = parts[parts.length - 1];
9215
- if (lastPart !== null && lastPart !== void 0) {
9216
- current[lastPart] = token.$value;
9217
- }
9218
- }
9219
- return result;
9089
+ return buildNestedTokenObject(tokens, (token) => token.$value);
9220
9090
  }
9221
- /**
9222
- * Nest tokens by path (with metadata)
9223
- */
9224
9091
  nestTokens(tokens) {
9225
- const result = {};
9226
- for (const [, token] of getSortedTokenEntries(tokens)) {
9227
- const parts = token.path;
9228
- let current = result;
9229
- for (let i = 0; i < parts.length - 1; i++) {
9230
- const part = parts[i];
9231
- if (part === null || part === void 0) {
9232
- continue;
9233
- }
9234
- if (!(part in current)) {
9235
- current[part] = {};
9236
- }
9237
- current = current[part];
9238
- }
9239
- const lastPart = parts[parts.length - 1];
9240
- if (lastPart !== null && lastPart !== void 0) {
9241
- current[lastPart] = this.serializeToken(token);
9242
- }
9243
- }
9244
- return result;
9092
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
9245
9093
  }
9246
9094
  serializeToken(token) {
9247
9095
  return {
@@ -9252,9 +9100,6 @@ var JsonRenderer = class {
9252
9100
  ...token.$extensions != null && { $extensions: token.$extensions }
9253
9101
  };
9254
9102
  }
9255
- isBasePermutation(modifierInputs, defaults) {
9256
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9257
- }
9258
9103
  };
9259
9104
  function jsonRenderer() {
9260
9105
  const rendererInstance = new JsonRenderer();
@@ -9267,7 +9112,6 @@ function jsonRenderer() {
9267
9112
  }
9268
9113
 
9269
9114
  // src/renderers/tailwind.ts
9270
- init_errors();
9271
9115
  init_token_utils();
9272
9116
 
9273
9117
  // src/renderers/bundlers/tailwind.ts
@@ -9296,6 +9140,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
9296
9140
  }
9297
9141
  return cssBlocks.join("\n");
9298
9142
  }
9143
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
9144
+ const normalized = normalizeModifierInputs(modifierInputs);
9145
+ return {
9146
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
9147
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
9148
+ };
9149
+ }
9299
9150
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
9300
9151
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
9301
9152
  if (differenceCount > 1) {
@@ -9308,19 +9159,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
9308
9159
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
9309
9160
  const [modifier, context] = parseModifierSource(expectedSource);
9310
9161
  const cleanTokens = stripInternalMetadata(tokensToInclude);
9311
- const selector = resolveSelector(
9312
- options.selector,
9162
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
9163
+ options,
9313
9164
  modifier,
9314
9165
  context,
9315
- false,
9316
- normalizeModifierInputs(modifierInputs)
9317
- );
9318
- const mediaQuery = resolveMediaQuery(
9319
- options.mediaQuery,
9320
- modifier,
9321
- context,
9322
- false,
9323
- normalizeModifierInputs(modifierInputs)
9166
+ modifierInputs
9324
9167
  );
9325
9168
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
9326
9169
  return `/* Modifier: ${modifier}=${context} */
@@ -9409,7 +9252,7 @@ var TailwindRenderer = class {
9409
9252
  */
9410
9253
  async formatTokens(tokens, options) {
9411
9254
  const lines = [];
9412
- const indent2 = options.minify ? "" : " ";
9255
+ const indent = options.minify ? "" : " ";
9413
9256
  const newline = options.minify ? "" : "\n";
9414
9257
  const space = options.minify ? "" : " ";
9415
9258
  if (options.includeImport) {
@@ -9431,7 +9274,7 @@ var TailwindRenderer = class {
9431
9274
  for (const [, token] of getSortedTokenEntries(tokens)) {
9432
9275
  const varName = this.buildVariableName(token);
9433
9276
  const varValue = this.formatValue(token);
9434
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
9277
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
9435
9278
  }
9436
9279
  lines.push(`}${newline}`);
9437
9280
  const cssString = lines.join("");
@@ -9442,15 +9285,15 @@ var TailwindRenderer = class {
9442
9285
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
9443
9286
  */
9444
9287
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
9445
- const indent2 = minify ? "" : " ";
9288
+ const indent = minify ? "" : " ";
9446
9289
  const newline = minify ? "" : "\n";
9447
9290
  const space = minify ? "" : " ";
9448
9291
  const hasMediaQuery = mediaQuery !== "";
9449
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
9292
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
9450
9293
  const lines = [];
9451
9294
  if (hasMediaQuery) {
9452
9295
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
9453
- lines.push(`${indent2}${selector}${space}{${newline}`);
9296
+ lines.push(`${indent}${selector}${space}{${newline}`);
9454
9297
  } else {
9455
9298
  lines.push(`${selector}${space}{${newline}`);
9456
9299
  }
@@ -9460,7 +9303,7 @@ var TailwindRenderer = class {
9460
9303
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
9461
9304
  }
9462
9305
  if (hasMediaQuery) {
9463
- lines.push(`${indent2}}${newline}`);
9306
+ lines.push(`${indent}}${newline}`);
9464
9307
  lines.push(`}${newline}`);
9465
9308
  } else {
9466
9309
  lines.push(`}${newline}`);
@@ -9487,8 +9330,8 @@ var TailwindRenderer = class {
9487
9330
  if (token.$type === "dimension" && isDimensionObject(value)) {
9488
9331
  return dimensionObjectToString(value);
9489
9332
  }
9490
- if (token.$type === "duration" && this.isDurationObject(value)) {
9491
- return `${value.value}${value.unit}`;
9333
+ if (token.$type === "duration" && isDurationObject(value)) {
9334
+ return durationObjectToString(value);
9492
9335
  }
9493
9336
  if (token.$type === "fontFamily") {
9494
9337
  if (Array.isArray(value)) {
@@ -9543,9 +9386,6 @@ var TailwindRenderer = class {
9543
9386
  }
9544
9387
  return parts.join(" ");
9545
9388
  }
9546
- isDurationObject(value) {
9547
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
9548
- }
9549
9389
  async formatWithPrettier(css2) {
9550
9390
  try {
9551
9391
  return await prettier.format(css2, {
@@ -9562,7 +9402,7 @@ var TailwindRenderer = class {
9562
9402
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9563
9403
  tokens,
9564
9404
  modifierInputs,
9565
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9405
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9566
9406
  }));
9567
9407
  return await bundleAsTailwind(
9568
9408
  bundleData,
@@ -9572,12 +9412,12 @@ var TailwindRenderer = class {
9572
9412
  );
9573
9413
  }
9574
9414
  async formatStandalone(context, options) {
9575
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9576
- if (!context.output.file && requiresFile) {
9577
- throw new ConfigurationError(
9578
- `Output "${context.output.name}": file is required for standalone Tailwind output`
9579
- );
9580
- }
9415
+ assertFileRequired(
9416
+ context.buildPath,
9417
+ context.output.file,
9418
+ context.output.name,
9419
+ "standalone Tailwind"
9420
+ );
9581
9421
  const files = {};
9582
9422
  for (const { tokens, modifierInputs } of context.permutations) {
9583
9423
  const processedTokens = stripInternalMetadata(tokens);
@@ -9593,11 +9433,6 @@ var TailwindRenderer = class {
9593
9433
  }
9594
9434
  return outputTree(files);
9595
9435
  }
9596
- isBasePermutation(modifierInputs, defaults) {
9597
- return Object.entries(defaults).every(
9598
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
9599
- );
9600
- }
9601
9436
  };
9602
9437
  function tailwindRenderer() {
9603
9438
  const rendererInstance = new TailwindRenderer();
@@ -9730,11 +9565,6 @@ function defineRenderer(renderer) {
9730
9565
 
9731
9566
  // src/index.ts
9732
9567
  init_errors();
9733
- /**
9734
- * @license
9735
- * Copyright (c) 2025 Dispersa Contributors
9736
- * SPDX-License-Identifier: MIT
9737
- */
9738
9568
  /**
9739
9569
  * @license MIT
9740
9570
  * Copyright (c) 2025-present Dispersa Contributors
@@ -9742,7 +9572,12 @@ init_errors();
9742
9572
  * This source code is licensed under the MIT license found in the
9743
9573
  * LICENSE file in the root directory of this source tree.
9744
9574
  */
9575
+ /**
9576
+ * @license
9577
+ * Copyright (c) 2025 Dispersa Contributors
9578
+ * SPDX-License-Identifier: MIT
9579
+ */
9745
9580
 
9746
- export { BasePermutationError, CircularReferenceError, ColorParseError, ConfigurationError, DimensionFormatError, Dispersa, DispersaError, FileOperationError, ModifierError, TokenReferenceError, ValidationError, android, css, defineRenderer, ios, isBorderToken, isColorToken, isDimensionToken, isDurationToken, isGradientToken, isOutputTree, isShadowToken, isTransitionToken, isTypographyToken, js, json, outputTree, tailwind };
9581
+ export { BasePermutationError, CircularReferenceError, ConfigurationError, Dispersa, DispersaError, FileOperationError, ModifierError, TokenReferenceError, ValidationError, android, css, defineRenderer, ios, isBorderToken, isColorToken, isDimensionToken, isDurationToken, isGradientToken, isOutputTree, isShadowToken, isTransitionToken, isTypographyToken, js, json, outputTree, tailwind };
9747
9582
  //# sourceMappingURL=index.js.map
9748
9583
  //# sourceMappingURL=index.js.map