dispersa 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { constants } from 'fs';
4
4
  import { mkdir, writeFile, access, readFile } from 'fs/promises';
5
5
  import * as path from 'path';
6
6
  import { JsonPointer } from 'json-ptr';
7
+ import { kebabCase } from 'change-case';
7
8
  import { converter, formatHex8, formatHex } from 'culori';
8
9
  import prettier from 'prettier';
9
10
 
@@ -17,47 +18,6 @@ var __export = (target, all) => {
17
18
  __defProp(target, name, { get: all[name], enumerable: true });
18
19
  };
19
20
 
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
21
  // src/shared/errors/index.ts
62
22
  var DispersaError, TokenReferenceError, CircularReferenceError, ValidationError, FileOperationError, ConfigurationError, BasePermutationError, ModifierError;
63
23
  var init_errors = __esm({
@@ -145,6 +105,70 @@ var init_errors = __esm({
145
105
  }
146
106
  });
147
107
 
108
+ // src/shared/utils/token-utils.ts
109
+ function formatDeprecationMessage(token, description = "", format = "bracket") {
110
+ if (token.$deprecated == null || token.$deprecated === false) {
111
+ return description;
112
+ }
113
+ const deprecationMsg = typeof token.$deprecated === "string" ? token.$deprecated : "";
114
+ if (format === "comment") {
115
+ const msg2 = deprecationMsg ? ` ${deprecationMsg}` : "";
116
+ return `DEPRECATED${msg2}`;
117
+ }
118
+ const msg = deprecationMsg ? `: ${deprecationMsg}` : "";
119
+ const prefix = `[DEPRECATED${msg}]`;
120
+ return description ? `${prefix} ${description}` : prefix;
121
+ }
122
+ function stripInternalTokenMetadata(tokens) {
123
+ const cleaned = {};
124
+ for (const [name, token] of Object.entries(tokens)) {
125
+ const { _isAlias: _alias, _sourceModifier: _source, _sourceSet, ...rest } = token;
126
+ cleaned[name] = rest;
127
+ }
128
+ return cleaned;
129
+ }
130
+ function getSortedTokenEntries(tokens) {
131
+ return Object.entries(tokens).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
132
+ }
133
+ function buildNestedTokenObject(tokens, extractValue) {
134
+ const result = {};
135
+ for (const [, token] of getSortedTokenEntries(tokens)) {
136
+ setNestedValue(result, token.path, extractValue(token));
137
+ }
138
+ return result;
139
+ }
140
+ function setNestedValue(root, path7, value) {
141
+ let current = root;
142
+ for (let i = 0; i < path7.length - 1; i++) {
143
+ const part = path7[i];
144
+ if (part == null) {
145
+ continue;
146
+ }
147
+ if (!(part in current)) {
148
+ current[part] = {};
149
+ }
150
+ current = current[part];
151
+ }
152
+ const lastPart = path7[path7.length - 1];
153
+ if (lastPart != null) {
154
+ current[lastPart] = value;
155
+ }
156
+ }
157
+ function isTokenLike(value) {
158
+ return typeof value === "object" && value !== null && ("$value" in value || "$ref" in value);
159
+ }
160
+ function getPureAliasReferenceName(value) {
161
+ if (typeof value !== "string") {
162
+ return void 0;
163
+ }
164
+ const match = /^\{([^}]+)\}$/.exec(value);
165
+ return match?.[1]?.trim();
166
+ }
167
+ var init_token_utils = __esm({
168
+ "src/shared/utils/token-utils.ts"() {
169
+ }
170
+ });
171
+
148
172
  // src/shared/utils/validation-handler.ts
149
173
  var ValidationHandler;
150
174
  var init_validation_handler = __esm({
@@ -2884,30 +2908,29 @@ var init_validator = __esm({
2884
2908
  validateTokenOrGroup(obj) {
2885
2909
  const hasValue = isTokenLike(obj);
2886
2910
  if (hasValue) {
2887
- const tokenErrors = this.validateToken(obj);
2888
- if (tokenErrors.length === 0) {
2911
+ const tokenErrors2 = this.validateToken(obj);
2912
+ if (tokenErrors2.length === 0) {
2889
2913
  return { type: "token", errors: [] };
2890
2914
  }
2891
2915
  return {
2892
2916
  type: "invalid",
2893
- errors: tokenErrors,
2917
+ errors: tokenErrors2,
2894
2918
  message: "Object has $value/$ref but failed token validation"
2895
2919
  };
2896
- } else {
2897
- const groupErrors = this.validateGroup(obj);
2898
- if (groupErrors.length === 0) {
2899
- return { type: "group", errors: [] };
2900
- }
2901
- const tokenErrors = this.validateToken(obj);
2902
- if (tokenErrors.length === 0) {
2903
- return { type: "token", errors: [] };
2904
- }
2905
- return {
2906
- type: "invalid",
2907
- errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2908
- message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2909
- };
2910
2920
  }
2921
+ const groupErrors = this.validateGroup(obj);
2922
+ if (groupErrors.length === 0) {
2923
+ return { type: "group", errors: [] };
2924
+ }
2925
+ const tokenErrors = this.validateToken(obj);
2926
+ if (tokenErrors.length === 0) {
2927
+ return { type: "token", errors: [] };
2928
+ }
2929
+ return {
2930
+ type: "invalid",
2931
+ errors: groupErrors.length < tokenErrors.length ? groupErrors : tokenErrors,
2932
+ message: groupErrors.length < tokenErrors.length ? "Object appears to be a group but failed validation" : "Object appears to be a token but failed validation"
2933
+ };
2911
2934
  }
2912
2935
  /**
2913
2936
  * Format AJV errors into readable ValidationError objects
@@ -3102,35 +3125,34 @@ var init_resolver_parser = __esm({
3102
3125
  this.validateSourceReferences(set.sources, `set "${setName}"`, contextMsg);
3103
3126
  }
3104
3127
  }
3105
- if (resolver.modifiers) {
3106
- for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3107
- for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3108
- this.validateSourceReferences(
3109
- sources,
3110
- `modifier "${modifierName}" context "${contextName}"`,
3111
- contextMsg
3112
- );
3113
- }
3128
+ if (!resolver.modifiers) {
3129
+ return;
3130
+ }
3131
+ for (const [modifierName, modifier] of Object.entries(resolver.modifiers)) {
3132
+ for (const [contextName, sources] of Object.entries(modifier.contexts)) {
3133
+ this.validateSourceReferences(
3134
+ sources,
3135
+ `modifier "${modifierName}" context "${contextName}"`,
3136
+ contextMsg
3137
+ );
3114
3138
  }
3115
3139
  }
3116
3140
  }
3117
- /**
3118
- * Validate source references in an array
3119
- */
3120
3141
  validateSourceReferences(sources, location, contextMsg) {
3121
3142
  for (const source of sources) {
3122
- if (typeof source === "object" && source !== null && "$ref" in source) {
3123
- const ref = source.$ref;
3124
- if (ref.startsWith("#/modifiers/")) {
3125
- this.handleValidationIssue(
3126
- `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3127
- );
3128
- }
3129
- if (ref.startsWith("#/resolutionOrder/")) {
3130
- this.handleValidationIssue(
3131
- `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3132
- );
3133
- }
3143
+ if (typeof source !== "object" || source === null || !("$ref" in source)) {
3144
+ continue;
3145
+ }
3146
+ const ref = source.$ref;
3147
+ if (ref.startsWith("#/modifiers/")) {
3148
+ this.handleValidationIssue(
3149
+ `Invalid reference in ${location}: "${ref}". Sets and modifier contexts MUST NOT reference modifiers${contextMsg}`
3150
+ );
3151
+ }
3152
+ if (ref.startsWith("#/resolutionOrder/")) {
3153
+ this.handleValidationIssue(
3154
+ `Invalid reference in ${location}: "${ref}". References MUST NOT point to resolutionOrder array items${contextMsg}`
3155
+ );
3134
3156
  }
3135
3157
  }
3136
3158
  }
@@ -3210,15 +3232,14 @@ var init_resolver_loader = __esm({
3210
3232
  * ```
3211
3233
  */
3212
3234
  async load(resolver) {
3213
- if (typeof resolver === "string") {
3214
- const absolutePath = path.resolve(this.options.baseDir, resolver);
3215
- const resolverDoc = await this.parser.parseFile(absolutePath);
3216
- const baseDir = path.dirname(absolutePath);
3217
- return { resolverDoc, baseDir };
3218
- } else {
3219
- const resolverDoc = this.parser.parseInline(resolver);
3220
- return { resolverDoc, baseDir: this.options.baseDir };
3235
+ if (typeof resolver !== "string") {
3236
+ const resolverDoc2 = this.parser.parseInline(resolver);
3237
+ return { resolverDoc: resolverDoc2, baseDir: this.options.baseDir };
3221
3238
  }
3239
+ const absolutePath = path.resolve(this.options.baseDir, resolver);
3240
+ const resolverDoc = await this.parser.parseFile(absolutePath);
3241
+ const baseDir = path.dirname(absolutePath);
3242
+ return { resolverDoc, baseDir };
3222
3243
  }
3223
3244
  /**
3224
3245
  * Load only the resolver document (without base directory)
@@ -3251,6 +3272,35 @@ function sanitizeDataAttributeName(value) {
3251
3272
  function escapeCssString(value) {
3252
3273
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ");
3253
3274
  }
3275
+ function groupTokensByType(tokens, typeGroupMap) {
3276
+ const groupMap = /* @__PURE__ */ new Map();
3277
+ for (const [, token] of getSortedTokenEntries(tokens)) {
3278
+ const groupName = typeGroupMap[token.$type ?? ""] ?? "Other";
3279
+ const existing = groupMap.get(groupName) ?? [];
3280
+ existing.push(token);
3281
+ groupMap.set(groupName, existing);
3282
+ }
3283
+ return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
3284
+ name,
3285
+ tokens: groupTokens
3286
+ }));
3287
+ }
3288
+ function indentStr(width, level) {
3289
+ return " ".repeat(width * level);
3290
+ }
3291
+ function buildGeneratedFileHeader() {
3292
+ return [
3293
+ "// Generated by Dispersa - do not edit manually",
3294
+ "// https://github.com/dispersa-core/dispersa"
3295
+ ].join("\n");
3296
+ }
3297
+ function toSafeIdentifier(name, keywords, capitalize) {
3298
+ const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
3299
+ const cased = capitalize ? camel.charAt(0).toUpperCase() + camel.slice(1) : camel.charAt(0).toLowerCase() + camel.slice(1);
3300
+ const safe = /^\d/.test(cased) ? `_${cased}` : cased;
3301
+ const keyCheck = capitalize ? safe.charAt(0).toLowerCase() + safe.slice(1) : safe;
3302
+ return keywords.has(keyCheck) ? `\`${safe}\`` : safe;
3303
+ }
3254
3304
  function normalizeModifierInputs(inputs) {
3255
3305
  const normalized = {};
3256
3306
  for (const [key, value] of Object.entries(inputs)) {
@@ -3258,6 +3308,14 @@ function normalizeModifierInputs(inputs) {
3258
3308
  }
3259
3309
  return normalized;
3260
3310
  }
3311
+ function assertFileRequired(buildPath, outputFile, outputName, presetLabel) {
3312
+ const requiresFile = buildPath !== void 0 && buildPath !== "";
3313
+ if (!outputFile && requiresFile) {
3314
+ throw new ConfigurationError(
3315
+ `Output "${outputName}": file is required for ${presetLabel} output`
3316
+ );
3317
+ }
3318
+ }
3261
3319
  function buildStablePermutationKey(modifierInputs, dimensions) {
3262
3320
  return dimensions.map((dimension) => `${dimension}=${modifierInputs[dimension] ?? ""}`).join("|");
3263
3321
  }
@@ -3307,14 +3365,18 @@ function generatePermutationKey(modifierInputs, resolver, isBase) {
3307
3365
  }
3308
3366
  return buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3309
3367
  }
3310
- function buildInMemoryOutputKey(params) {
3311
- const { outputName, extension, modifierInputs, resolver, defaults } = params;
3368
+ function isBasePermutation(modifierInputs, defaults) {
3312
3369
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3313
3370
  const normalizedDefaults = normalizeModifierInputs(defaults);
3314
- const isBase = Object.entries(normalizedDefaults).every(
3315
- ([key, value]) => normalizedInputs[key] === value
3371
+ return Object.entries(normalizedDefaults).every(([key, value]) => normalizedInputs[key] === value);
3372
+ }
3373
+ function buildInMemoryOutputKey(params) {
3374
+ const { outputName, extension, modifierInputs, resolver, defaults } = params;
3375
+ const permutationKey = generatePermutationKey(
3376
+ modifierInputs,
3377
+ resolver,
3378
+ isBasePermutation(modifierInputs, defaults)
3316
3379
  );
3317
- const permutationKey = generatePermutationKey(modifierInputs, resolver, isBase);
3318
3380
  return `${outputName}-${permutationKey}.${extension}`;
3319
3381
  }
3320
3382
  function buildMetadata(resolver) {
@@ -3436,6 +3498,7 @@ function resolveFileName(fileName, modifierInputs) {
3436
3498
  }
3437
3499
  var init_utils = __esm({
3438
3500
  "src/renderers/bundlers/utils.ts"() {
3501
+ init_errors();
3439
3502
  init_token_utils();
3440
3503
  }
3441
3504
  });
@@ -3445,36 +3508,38 @@ var js_exports = {};
3445
3508
  __export(js_exports, {
3446
3509
  bundleAsJsModule: () => bundleAsJsModule
3447
3510
  });
3511
+ function updateStringTracking(state, char) {
3512
+ if (!state.escaped && (char === '"' || char === "'" || char === "`")) {
3513
+ if (!state.inString) {
3514
+ state.inString = true;
3515
+ state.stringChar = char;
3516
+ } else if (char === state.stringChar) {
3517
+ state.inString = false;
3518
+ state.stringChar = "";
3519
+ }
3520
+ }
3521
+ state.escaped = !state.escaped && char === "\\";
3522
+ }
3448
3523
  function extractObjectFromJsModule(formattedJs) {
3449
3524
  const assignmentMatch = /const\s+\w+\s*=\s*\{/.exec(formattedJs);
3450
3525
  if (!assignmentMatch) {
3451
3526
  return "{}";
3452
3527
  }
3453
3528
  const startIndex = assignmentMatch.index + assignmentMatch[0].length - 1;
3529
+ const state = { inString: false, stringChar: "", escaped: false };
3454
3530
  let braceCount = 0;
3455
- let inString = false;
3456
- let stringChar = "";
3457
- let escaped = false;
3458
3531
  for (let i = startIndex; i < formattedJs.length; i++) {
3459
3532
  const char = formattedJs[i];
3460
- if (!escaped && (char === '"' || char === "'" || char === "`")) {
3461
- if (!inString) {
3462
- inString = true;
3463
- stringChar = char;
3464
- } else if (char === stringChar) {
3465
- inString = false;
3466
- stringChar = "";
3467
- }
3468
- }
3469
- escaped = !escaped && char === "\\";
3470
- if (!inString) {
3471
- if (char === "{") {
3472
- braceCount++;
3473
- } else if (char === "}") {
3474
- braceCount--;
3475
- if (braceCount === 0) {
3476
- return formattedJs.substring(startIndex, i + 1);
3477
- }
3533
+ updateStringTracking(state, char);
3534
+ if (state.inString) {
3535
+ continue;
3536
+ }
3537
+ if (char === "{") {
3538
+ braceCount++;
3539
+ } else if (char === "}") {
3540
+ braceCount--;
3541
+ if (braceCount === 0) {
3542
+ return formattedJs.substring(startIndex, i + 1);
3478
3543
  }
3479
3544
  }
3480
3545
  }
@@ -3571,22 +3636,19 @@ __export(json_exports, {
3571
3636
  bundleAsJson: () => bundleAsJson
3572
3637
  });
3573
3638
  async function bundleAsJson(bundleData, resolver, formatTokens) {
3639
+ if (!formatTokens) {
3640
+ throw new ConfigurationError("JSON formatter was not provided");
3641
+ }
3574
3642
  const metadata = buildMetadata(resolver);
3575
3643
  const tokens = {};
3576
3644
  for (const { tokens: tokenSet, modifierInputs } of bundleData) {
3577
3645
  const cleanTokens = stripInternalMetadata(tokenSet);
3578
- if (!formatTokens) {
3579
- throw new ConfigurationError("JSON formatter was not provided");
3580
- }
3581
3646
  const normalizedInputs = normalizeModifierInputs(modifierInputs);
3582
3647
  const key = buildStablePermutationKey(normalizedInputs, metadata.dimensions);
3583
3648
  const themeJson = await formatTokens(cleanTokens);
3584
3649
  tokens[key] = JSON.parse(themeJson);
3585
3650
  }
3586
- const bundle = {
3587
- _meta: metadata,
3588
- tokens
3589
- };
3651
+ const bundle = { _meta: metadata, tokens };
3590
3652
  return JSON.stringify(bundle, null, 2);
3591
3653
  }
3592
3654
  var init_json = __esm({
@@ -3652,15 +3714,15 @@ var TypeGenerator = class {
3652
3714
  const lines = [];
3653
3715
  if (names.length === 0) {
3654
3716
  lines.push(`export type ${typeName} = never`);
3655
- } else {
3656
- lines.push(`export type ${typeName} =`);
3657
- for (let i = 0; i < names.length; i++) {
3658
- const name = names[i];
3659
- if (name == null) {
3660
- continue;
3661
- }
3662
- lines.push(` | "${name}"`);
3717
+ return lines;
3718
+ }
3719
+ lines.push(`export type ${typeName} =`);
3720
+ for (let i = 0; i < names.length; i++) {
3721
+ const name = names[i];
3722
+ if (name == null) {
3723
+ continue;
3663
3724
  }
3725
+ lines.push(` | "${name}"`);
3664
3726
  }
3665
3727
  return lines;
3666
3728
  }
@@ -3686,15 +3748,10 @@ var TypeGenerator = class {
3686
3748
  generateStructureType(tokens, options) {
3687
3749
  const lines = [];
3688
3750
  const structure = this.buildNestedStructure(tokens);
3689
- if (options.exportType === "type") {
3690
- lines.push(`export type ${options.moduleName} = {`);
3691
- this.addStructureProperties(lines, structure, 1);
3692
- lines.push("}");
3693
- } else {
3694
- lines.push(`export interface ${options.moduleName} {`);
3695
- this.addStructureProperties(lines, structure, 1);
3696
- lines.push("}");
3697
- }
3751
+ const opener = options.exportType === "type" ? `export type ${options.moduleName} = {` : `export interface ${options.moduleName} {`;
3752
+ lines.push(opener);
3753
+ this.addStructureProperties(lines, structure, 1);
3754
+ lines.push("}");
3698
3755
  return lines;
3699
3756
  }
3700
3757
  /**
@@ -3728,20 +3785,20 @@ var TypeGenerator = class {
3728
3785
  /**
3729
3786
  * Add structure properties to lines
3730
3787
  */
3731
- addStructureProperties(lines, structure, indent2) {
3732
- const indentStr = " ".repeat(indent2);
3788
+ addStructureProperties(lines, structure, indent) {
3789
+ const indentStr2 = " ".repeat(indent);
3733
3790
  for (const [key, value] of Object.entries(structure)) {
3734
3791
  if (this.isToken(value)) {
3735
3792
  const token = value;
3736
3793
  if (token.$description) {
3737
- lines.push(`${indentStr}/** ${token.$description} */`);
3794
+ lines.push(`${indentStr2}/** ${token.$description} */`);
3738
3795
  }
3739
3796
  const valueType = this.inferValueType(token);
3740
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueType}`);
3797
+ lines.push(`${indentStr2}${this.quoteKey(key)}: ${valueType}`);
3741
3798
  } else {
3742
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
3743
- this.addStructureProperties(lines, value, indent2 + 1);
3744
- lines.push(`${indentStr}}`);
3799
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
3800
+ this.addStructureProperties(lines, value, indent + 1);
3801
+ lines.push(`${indentStr2}}`);
3745
3802
  }
3746
3803
  }
3747
3804
  }
@@ -3933,28 +3990,27 @@ var BuildOrchestrator = class {
3933
3990
  if (config.hooks?.onBuildStart) {
3934
3991
  await config.hooks.onBuildStart({ config, resolver });
3935
3992
  }
3936
- let permutations;
3937
- if (config.permutations && config.permutations.length > 0) {
3938
- permutations = await Promise.all(
3939
- config.permutations.map(async (modifierInputs) => {
3940
- const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
3941
- resolver,
3942
- modifierInputs,
3943
- config.transforms,
3944
- config.preprocessors,
3945
- config.filters
3946
- );
3947
- return { tokens, modifierInputs: resolvedInputs };
3948
- })
3949
- );
3950
- } else {
3951
- permutations = await this.pipeline.resolveAllPermutations(
3993
+ if (!config.permutations || config.permutations.length === 0) {
3994
+ const permutations2 = await this.pipeline.resolveAllPermutations(
3952
3995
  resolver,
3953
3996
  config.transforms,
3954
3997
  config.preprocessors,
3955
3998
  config.filters
3956
3999
  );
4000
+ return this.executeBuild(buildPath, config, permutations2, resolver);
3957
4001
  }
4002
+ const permutations = await Promise.all(
4003
+ config.permutations.map(async (modifierInputs) => {
4004
+ const { tokens, modifierInputs: resolvedInputs } = await this.pipeline.resolve(
4005
+ resolver,
4006
+ modifierInputs,
4007
+ config.transforms,
4008
+ config.preprocessors,
4009
+ config.filters
4010
+ );
4011
+ return { tokens, modifierInputs: resolvedInputs };
4012
+ })
4013
+ );
3958
4014
  return this.executeBuild(buildPath, config, permutations, resolver);
3959
4015
  }
3960
4016
  /**
@@ -3976,44 +4032,15 @@ var BuildOrchestrator = class {
3976
4032
  * @returns Build result with success status, outputs, and any errors
3977
4033
  */
3978
4034
  async executeBuild(buildPath, config, permutations, resolver) {
3979
- const outputs = [];
3980
- const errors = [];
3981
4035
  try {
3982
4036
  const resolverDoc = await resolveResolverDocument(resolver);
3983
4037
  const metadata = buildMetadata(resolverDoc);
3984
- const basePermutation = metadata.defaults;
3985
4038
  const settled = await Promise.allSettled(
3986
- config.outputs.map(async (output) => {
3987
- if (output.hooks?.onBuildStart) {
3988
- await output.hooks.onBuildStart({ config, resolver });
3989
- }
3990
- try {
3991
- const results = await this.processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath);
3992
- if (output.hooks?.onBuildEnd) {
3993
- await output.hooks.onBuildEnd({ success: true, outputs: results });
3994
- }
3995
- return results;
3996
- } catch (error) {
3997
- if (output.hooks?.onBuildEnd) {
3998
- await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
3999
- }
4000
- throw error;
4001
- }
4002
- })
4039
+ config.outputs.map(
4040
+ (output) => this.buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver)
4041
+ )
4003
4042
  );
4004
- for (let i = 0; i < settled.length; i++) {
4005
- const outcome = settled[i];
4006
- if (outcome.status === "fulfilled") {
4007
- outputs.push(...outcome.value);
4008
- } else {
4009
- errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4010
- }
4011
- }
4012
- const result = {
4013
- success: errors.length === 0,
4014
- outputs,
4015
- errors: errors.length > 0 ? errors : void 0
4016
- };
4043
+ const result = this.collectSettledResults(settled, config);
4017
4044
  if (config.hooks?.onBuildEnd) {
4018
4045
  await config.hooks.onBuildEnd(result);
4019
4046
  }
@@ -4030,6 +4057,40 @@ var BuildOrchestrator = class {
4030
4057
  return result;
4031
4058
  }
4032
4059
  }
4060
+ async buildSingleOutput(output, permutations, resolverDoc, metadata, buildPath, config, resolver) {
4061
+ if (output.hooks?.onBuildStart) {
4062
+ await output.hooks.onBuildStart({ config, resolver });
4063
+ }
4064
+ try {
4065
+ const results = await this.processOutput(output, permutations, resolverDoc, metadata, metadata.defaults, buildPath);
4066
+ if (output.hooks?.onBuildEnd) {
4067
+ await output.hooks.onBuildEnd({ success: true, outputs: results });
4068
+ }
4069
+ return results;
4070
+ } catch (error) {
4071
+ if (output.hooks?.onBuildEnd) {
4072
+ await output.hooks.onBuildEnd({ success: false, outputs: [], errors: [toBuildError(error, output.name)] });
4073
+ }
4074
+ throw error;
4075
+ }
4076
+ }
4077
+ collectSettledResults(settled, config) {
4078
+ const outputs = [];
4079
+ const errors = [];
4080
+ for (let i = 0; i < settled.length; i++) {
4081
+ const outcome = settled[i];
4082
+ if (outcome.status === "fulfilled") {
4083
+ outputs.push(...outcome.value);
4084
+ } else {
4085
+ errors.push(toBuildError(outcome.reason, config.outputs[i].name));
4086
+ }
4087
+ }
4088
+ return {
4089
+ success: errors.length === 0,
4090
+ outputs,
4091
+ errors: errors.length > 0 ? errors : void 0
4092
+ };
4093
+ }
4033
4094
  async processOutput(output, permutations, resolverDoc, metadata, basePermutation, buildPath) {
4034
4095
  if (!output.renderer.format) {
4035
4096
  throw new ConfigurationError("Renderer does not implement format()");
@@ -4079,7 +4140,7 @@ async function writeOutputFile(fileName, content, encoding = "utf-8") {
4079
4140
  }
4080
4141
  }
4081
4142
 
4082
- // src/processing/token-modifier.ts
4143
+ // src/processing/apply.ts
4083
4144
  function applyTransforms(tokens, transformList) {
4084
4145
  const result = {};
4085
4146
  for (const [name, token] of Object.entries(tokens)) {
@@ -5357,14 +5418,15 @@ var ResolutionEngine = class {
5357
5418
  mergeTokens(target, source) {
5358
5419
  const result = { ...target };
5359
5420
  for (const [key, value] of Object.entries(source)) {
5360
- if (typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value)) {
5361
- result[key] = this.mergeTokens(
5362
- result[key] ?? {},
5363
- value
5364
- );
5365
- } else {
5421
+ const isGroup = typeof value === "object" && value !== null && !Array.isArray(value) && !("$value" in value);
5422
+ if (!isGroup) {
5366
5423
  result[key] = value;
5424
+ continue;
5367
5425
  }
5426
+ result[key] = this.mergeTokens(
5427
+ result[key] ?? {},
5428
+ value
5429
+ );
5368
5430
  }
5369
5431
  return result;
5370
5432
  }
@@ -6453,6 +6515,17 @@ function isTransitionToken(token) {
6453
6515
  function isGradientToken(token) {
6454
6516
  return token.$type === "gradient";
6455
6517
  }
6518
+ function nameKebabCase() {
6519
+ return {
6520
+ transform: (token) => {
6521
+ const name = kebabCase(token.path.join(" "));
6522
+ return {
6523
+ ...token,
6524
+ name
6525
+ };
6526
+ }
6527
+ };
6528
+ }
6456
6529
  function isColorObject(value) {
6457
6530
  return typeof value === "object" && value !== null && "colorSpace" in value && "components" in value;
6458
6531
  }
@@ -6510,7 +6583,7 @@ function colorObjectToHex(color) {
6510
6583
  return formatHex(culoriColor);
6511
6584
  }
6512
6585
 
6513
- // src/processing/processors/transforms/built-in/dimension-converter.ts
6586
+ // src/processing/transforms/built-in/dimension-converter.ts
6514
6587
  function isDimensionObject(value) {
6515
6588
  return typeof value === "object" && value !== null && "value" in value && "unit" in value;
6516
6589
  }
@@ -6518,6 +6591,14 @@ function dimensionObjectToString(dimension) {
6518
6591
  return `${dimension.value}${dimension.unit}`;
6519
6592
  }
6520
6593
 
6594
+ // src/processing/transforms/built-in/duration-converter.ts
6595
+ function isDurationObject(value) {
6596
+ return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
6597
+ }
6598
+ function durationObjectToString(duration) {
6599
+ return `${duration.value}${duration.unit}`;
6600
+ }
6601
+
6521
6602
  // src/renderers/android.ts
6522
6603
  init_errors();
6523
6604
  init_token_utils();
@@ -6569,9 +6650,6 @@ function resolveColorFormat(format) {
6569
6650
  }
6570
6651
  return "argb_hex";
6571
6652
  }
6572
- function indent(width, level) {
6573
- return " ".repeat(width * level);
6574
- }
6575
6653
  function escapeKotlinString(str) {
6576
6654
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\$/g, "\\$");
6577
6655
  }
@@ -6587,22 +6665,6 @@ function roundComponent(value) {
6587
6665
  function toResourceName(family) {
6588
6666
  return family.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
6589
6667
  }
6590
- function toPascalCase(name) {
6591
- const pascal = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6592
- const result = pascal.charAt(0).toUpperCase() + pascal.slice(1);
6593
- if (/^\d/.test(result)) {
6594
- return `_${result}`;
6595
- }
6596
- return KOTLIN_KEYWORDS.has(result.charAt(0).toLowerCase() + result.slice(1)) ? `\`${result}\`` : result;
6597
- }
6598
- function toKotlinIdentifier(name) {
6599
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
6600
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
6601
- if (/^\d/.test(identifier)) {
6602
- return `_${identifier}`;
6603
- }
6604
- return KOTLIN_KEYWORDS.has(identifier) ? `\`${identifier}\`` : identifier;
6605
- }
6606
6668
  var AndroidRenderer = class {
6607
6669
  async format(context, options) {
6608
6670
  if (!options?.packageName) {
@@ -6610,6 +6672,7 @@ var AndroidRenderer = class {
6610
6672
  `Output "${context.output.name}": packageName is required for Android output`
6611
6673
  );
6612
6674
  }
6675
+ const visibility = options?.visibility;
6613
6676
  const opts = {
6614
6677
  preset: options?.preset ?? "standalone",
6615
6678
  packageName: options.packageName,
@@ -6617,7 +6680,8 @@ var AndroidRenderer = class {
6617
6680
  colorFormat: resolveColorFormat(options?.colorFormat),
6618
6681
  colorSpace: options?.colorSpace ?? "sRGB",
6619
6682
  structure: options?.structure ?? "nested",
6620
- visibility: options?.visibility,
6683
+ visibility,
6684
+ visPrefix: visibility ? `${visibility} ` : "",
6621
6685
  indent: options?.indent ?? 4
6622
6686
  };
6623
6687
  if (opts.preset === "bundle") {
@@ -6650,19 +6714,6 @@ var AndroidRenderer = class {
6650
6714
  // -----------------------------------------------------------------------
6651
6715
  // Flat structure grouping
6652
6716
  // -----------------------------------------------------------------------
6653
- groupTokensByType(tokens) {
6654
- const groupMap = /* @__PURE__ */ new Map();
6655
- for (const [, token] of getSortedTokenEntries(tokens)) {
6656
- const groupName = KOTLIN_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
6657
- const existing = groupMap.get(groupName) ?? [];
6658
- existing.push(token);
6659
- groupMap.set(groupName, existing);
6660
- }
6661
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
6662
- name,
6663
- tokens: groupTokens
6664
- }));
6665
- }
6666
6717
  /**
6667
6718
  * Builds a flattened camelCase name from a token's path, stripping the
6668
6719
  * type prefix segment (which is already represented by the group object).
@@ -6671,7 +6722,7 @@ var AndroidRenderer = class {
6671
6722
  const path7 = token.path;
6672
6723
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
6673
6724
  const joined = withoutTypePrefix.join("_");
6674
- return toKotlinIdentifier(joined);
6725
+ return toSafeIdentifier(joined, KOTLIN_KEYWORDS, false);
6675
6726
  }
6676
6727
  // -----------------------------------------------------------------------
6677
6728
  // Rendering
@@ -6683,22 +6734,21 @@ var AndroidRenderer = class {
6683
6734
  return this.formatAsNested(tokens, options);
6684
6735
  }
6685
6736
  formatAsNested(tokens, options) {
6737
+ const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6686
6738
  const tree = this.buildTokenTree(tokens);
6687
- const tokenTypes = /* @__PURE__ */ new Set();
6688
- this.collectTokenTypes(tree, tokenTypes);
6689
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6739
+ return this.buildFile(tokenTypes, options, (lines) => {
6690
6740
  lines.push(`@Suppress("unused")`);
6691
- lines.push(`${vis}object ${options.objectName} {`);
6741
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6692
6742
  this.renderTreeChildren(lines, tree, 1, options);
6693
6743
  lines.push("}");
6694
6744
  });
6695
6745
  }
6696
6746
  formatAsFlat(tokens, options) {
6697
- const groups = this.groupTokensByType(tokens);
6747
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
6698
6748
  const tokenTypes = this.collectTokenTypesFromEntries(tokens);
6699
- return this.buildFile(tokenTypes, options, (lines, vis) => {
6749
+ return this.buildFile(tokenTypes, options, (lines) => {
6700
6750
  lines.push(`@Suppress("unused")`);
6701
- lines.push(`${vis}object ${options.objectName} {`);
6751
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
6702
6752
  this.renderFlatGroups(lines, groups, 1, options);
6703
6753
  lines.push("}");
6704
6754
  });
@@ -6709,9 +6759,8 @@ var AndroidRenderer = class {
6709
6759
  */
6710
6760
  buildFile(tokenTypes, options, renderBody) {
6711
6761
  const imports = this.collectImports(tokenTypes, options);
6712
- const vis = options.visibility ? `${options.visibility} ` : "";
6713
6762
  const lines = [];
6714
- lines.push(this.buildFileHeader());
6763
+ lines.push(buildGeneratedFileHeader());
6715
6764
  lines.push("");
6716
6765
  lines.push(`package ${options.packageName}`);
6717
6766
  lines.push("");
@@ -6722,19 +6771,18 @@ var AndroidRenderer = class {
6722
6771
  lines.push("");
6723
6772
  }
6724
6773
  if (tokenTypes.has("shadow")) {
6725
- lines.push(...this.buildShadowTokenClass(vis, options));
6774
+ lines.push(...this.buildShadowTokenClass(options));
6726
6775
  lines.push("");
6727
6776
  }
6728
- renderBody(lines, vis);
6777
+ renderBody(lines);
6729
6778
  lines.push("");
6730
6779
  return lines.join("\n");
6731
6780
  }
6732
6781
  renderFlatGroups(lines, groups, baseDepth, options) {
6733
- const vis = options.visibility ? `${options.visibility} ` : "";
6734
- const groupIndent = indent(options.indent, baseDepth);
6735
- const valIndent = indent(options.indent, baseDepth + 1);
6782
+ const groupIndent = indentStr(options.indent, baseDepth);
6783
+ const valIndent = indentStr(options.indent, baseDepth + 1);
6736
6784
  for (const group of groups) {
6737
- lines.push(`${groupIndent}${vis}object ${group.name} {`);
6785
+ lines.push(`${groupIndent}${options.visPrefix}object ${group.name} {`);
6738
6786
  for (const token of group.tokens) {
6739
6787
  const kotlinName = this.buildFlatKotlinName(token);
6740
6788
  const kotlinValue = this.formatKotlinValue(token, options, baseDepth + 1);
@@ -6742,23 +6790,24 @@ var AndroidRenderer = class {
6742
6790
  if (token.$description) {
6743
6791
  lines.push(`${valIndent}/** ${escapeKDoc(token.$description)} */`);
6744
6792
  }
6745
- lines.push(`${valIndent}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6793
+ lines.push(
6794
+ `${valIndent}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`
6795
+ );
6746
6796
  }
6747
6797
  lines.push(`${groupIndent}}`);
6748
6798
  lines.push("");
6749
6799
  }
6750
6800
  }
6751
6801
  renderTreeChildren(lines, node, depth, options) {
6752
- const vis = options.visibility ? `${options.visibility} ` : "";
6753
- const pad = indent(options.indent, depth);
6802
+ const pad = indentStr(options.indent, depth);
6754
6803
  const entries = Array.from(node.children.entries());
6755
6804
  for (let idx = 0; idx < entries.length; idx++) {
6756
6805
  const [key, child] = entries[idx];
6757
6806
  if (child.token && child.children.size === 0) {
6758
6807
  this.renderLeaf(lines, key, child.token, depth, options);
6759
6808
  } else if (child.children.size > 0 && !child.token) {
6760
- const objectName = toPascalCase(key);
6761
- lines.push(`${pad}${vis}object ${objectName} {`);
6809
+ const objectName = toSafeIdentifier(key, KOTLIN_KEYWORDS, true);
6810
+ lines.push(`${pad}${options.visPrefix}object ${objectName} {`);
6762
6811
  this.renderTreeChildren(lines, child, depth + 1, options);
6763
6812
  lines.push(`${pad}}`);
6764
6813
  if (idx < entries.length - 1) {
@@ -6771,30 +6820,23 @@ var AndroidRenderer = class {
6771
6820
  }
6772
6821
  }
6773
6822
  renderLeaf(lines, key, token, depth, options) {
6774
- const vis = options.visibility ? `${options.visibility} ` : "";
6775
- const pad = indent(options.indent, depth);
6776
- const kotlinName = toKotlinIdentifier(key);
6823
+ const pad = indentStr(options.indent, depth);
6824
+ const kotlinName = toSafeIdentifier(key, KOTLIN_KEYWORDS, false);
6777
6825
  const kotlinValue = this.formatKotlinValue(token, options, depth);
6778
6826
  const annotation = this.typeAnnotationSuffix(token);
6779
6827
  if (token.$description) {
6780
6828
  lines.push(`${pad}/** ${escapeKDoc(token.$description)} */`);
6781
6829
  }
6782
- lines.push(`${pad}${vis}val ${kotlinName}${annotation} = ${kotlinValue}`);
6783
- }
6784
- buildFileHeader() {
6785
- return [
6786
- "// Generated by Dispersa - do not edit manually",
6787
- "// https://github.com/timges/dispersa"
6788
- ].join("\n");
6830
+ lines.push(`${pad}${options.visPrefix}val ${kotlinName}${annotation} = ${kotlinValue}`);
6789
6831
  }
6790
6832
  // -----------------------------------------------------------------------
6791
6833
  // Shadow data class
6792
6834
  // -----------------------------------------------------------------------
6793
- buildShadowTokenClass(vis, options) {
6794
- const i1 = indent(options.indent, 1);
6835
+ buildShadowTokenClass(options) {
6836
+ const i1 = indentStr(options.indent, 1);
6795
6837
  return [
6796
6838
  "@Immutable",
6797
- `${vis}data class ShadowToken(`,
6839
+ `${options.visPrefix}data class ShadowToken(`,
6798
6840
  `${i1}val color: Color,`,
6799
6841
  `${i1}val elevation: Dp,`,
6800
6842
  `${i1}val offsetX: Dp,`,
@@ -6845,14 +6887,6 @@ var AndroidRenderer = class {
6845
6887
  }
6846
6888
  return Array.from(imports).sort();
6847
6889
  }
6848
- collectTokenTypes(node, types) {
6849
- if (node.token?.$type) {
6850
- types.add(node.token.$type);
6851
- }
6852
- for (const child of node.children.values()) {
6853
- this.collectTokenTypes(child, types);
6854
- }
6855
- }
6856
6890
  collectTokenTypesFromEntries(tokens) {
6857
6891
  const types = /* @__PURE__ */ new Set();
6858
6892
  for (const [, token] of Object.entries(tokens)) {
@@ -7079,9 +7113,8 @@ var AndroidRenderer = class {
7079
7113
  return map[name.toLowerCase()];
7080
7114
  }
7081
7115
  formatDurationValue(value) {
7082
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
7083
- const dur = value;
7084
- return dur.unit === "ms" ? `${dur.value}.milliseconds` : `${dur.value}.seconds`;
7116
+ if (isDurationObject(value)) {
7117
+ return value.unit === "ms" ? `${value.value}.milliseconds` : `${value.value}.seconds`;
7085
7118
  }
7086
7119
  return typeof value === "number" ? `${value}.milliseconds` : "0.milliseconds";
7087
7120
  }
@@ -7099,8 +7132,8 @@ var AndroidRenderer = class {
7099
7132
  const elevation = isDimensionObject(shadow.blur) ? this.formatDimensionValue(shadow.blur) : "0.dp";
7100
7133
  const offsetX = isDimensionObject(shadow.offsetX) ? this.formatDimensionValue(shadow.offsetX) : "0.dp";
7101
7134
  const offsetY = isDimensionObject(shadow.offsetY) ? this.formatDimensionValue(shadow.offsetY) : "0.dp";
7102
- const propIndent = indent(options.indent, depth + 1);
7103
- const closeIndent = indent(options.indent, depth);
7135
+ const propIndent = indentStr(options.indent, depth + 1);
7136
+ const closeIndent = indentStr(options.indent, depth);
7104
7137
  return [
7105
7138
  "ShadowToken(",
7106
7139
  `${propIndent}color = ${color},`,
@@ -7149,8 +7182,8 @@ var AndroidRenderer = class {
7149
7182
  if (parts.length === 0) {
7150
7183
  return "TextStyle()";
7151
7184
  }
7152
- const propIndent = indent(options.indent, depth + 1);
7153
- const closeIndent = indent(options.indent, depth);
7185
+ const propIndent = indentStr(options.indent, depth + 1);
7186
+ const closeIndent = indentStr(options.indent, depth);
7154
7187
  return `TextStyle(
7155
7188
  ${parts.map((p) => `${propIndent}${p}`).join(",\n")},
7156
7189
  ${closeIndent})`;
@@ -7159,12 +7192,12 @@ ${closeIndent})`;
7159
7192
  // Output: standalone
7160
7193
  // -----------------------------------------------------------------------
7161
7194
  async formatStandalone(context, options) {
7162
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7163
- if (!context.output.file && requiresFile) {
7164
- throw new ConfigurationError(
7165
- `Output "${context.output.name}": file is required for standalone Android output`
7166
- );
7167
- }
7195
+ assertFileRequired(
7196
+ context.buildPath,
7197
+ context.output.file,
7198
+ context.output.name,
7199
+ "standalone Android"
7200
+ );
7168
7201
  const files = {};
7169
7202
  for (const { tokens, modifierInputs } of context.permutations) {
7170
7203
  const processedTokens = stripInternalMetadata(tokens);
@@ -7184,12 +7217,12 @@ ${closeIndent})`;
7184
7217
  // Output: bundle
7185
7218
  // -----------------------------------------------------------------------
7186
7219
  async formatBundle(context, options) {
7187
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
7188
- if (!context.output.file && requiresFile) {
7189
- throw new ConfigurationError(
7190
- `Output "${context.output.name}": file is required for bundle Android output`
7191
- );
7192
- }
7220
+ assertFileRequired(
7221
+ context.buildPath,
7222
+ context.output.file,
7223
+ context.output.name,
7224
+ "bundle Android"
7225
+ );
7193
7226
  const content = this.formatBundleContent(context, options);
7194
7227
  const fileName = context.output.file ? resolveFileName(context.output.file, context.meta.basePermutation) : buildInMemoryOutputKey({
7195
7228
  outputName: context.output.name,
@@ -7202,15 +7235,15 @@ ${closeIndent})`;
7202
7235
  }
7203
7236
  formatBundleContent(context, options) {
7204
7237
  const allTokenTypes = this.collectAllPermutationTypes(context);
7205
- return this.buildFile(allTokenTypes, options, (lines, vis) => {
7206
- const i1 = indent(options.indent, 1);
7238
+ return this.buildFile(allTokenTypes, options, (lines) => {
7239
+ const i1 = indentStr(options.indent, 1);
7207
7240
  lines.push(`@Suppress("unused")`);
7208
- lines.push(`${vis}object ${options.objectName} {`);
7241
+ lines.push(`${options.visPrefix}object ${options.objectName} {`);
7209
7242
  for (let idx = 0; idx < context.permutations.length; idx++) {
7210
7243
  const { tokens, modifierInputs } = context.permutations[idx];
7211
7244
  const processedTokens = stripInternalMetadata(tokens);
7212
7245
  const permName = this.buildPermutationName(modifierInputs);
7213
- lines.push(`${i1}${vis}object ${permName} {`);
7246
+ lines.push(`${i1}${options.visPrefix}object ${permName} {`);
7214
7247
  this.renderBundleTokens(lines, processedTokens, options, 2);
7215
7248
  lines.push(`${i1}}`);
7216
7249
  if (idx < context.permutations.length - 1) {
@@ -7221,20 +7254,17 @@ ${closeIndent})`;
7221
7254
  });
7222
7255
  }
7223
7256
  collectAllPermutationTypes(context) {
7224
- const allTokenTypes = /* @__PURE__ */ new Set();
7257
+ const types = /* @__PURE__ */ new Set();
7225
7258
  for (const { tokens } of context.permutations) {
7226
- const processed = stripInternalMetadata(tokens);
7227
- for (const [, token] of Object.entries(processed)) {
7228
- if (token.$type) {
7229
- allTokenTypes.add(token.$type);
7230
- }
7259
+ for (const t of this.collectTokenTypesFromEntries(stripInternalMetadata(tokens))) {
7260
+ types.add(t);
7231
7261
  }
7232
7262
  }
7233
- return allTokenTypes;
7263
+ return types;
7234
7264
  }
7235
7265
  renderBundleTokens(lines, tokens, options, baseDepth) {
7236
7266
  if (options.structure === "flat") {
7237
- const groups = this.groupTokensByType(tokens);
7267
+ const groups = groupTokensByType(tokens, KOTLIN_TYPE_GROUP_MAP);
7238
7268
  this.renderFlatGroups(lines, groups, baseDepth, options);
7239
7269
  return;
7240
7270
  }
@@ -7246,7 +7276,7 @@ ${closeIndent})`;
7246
7276
  if (values.length === 0) {
7247
7277
  return "Default";
7248
7278
  }
7249
- return values.map((v) => toPascalCase(v)).join("");
7279
+ return values.map((v) => toSafeIdentifier(v, KOTLIN_KEYWORDS, true)).join("");
7250
7280
  }
7251
7281
  };
7252
7282
  function androidRenderer() {
@@ -7266,19 +7296,19 @@ init_token_utils();
7266
7296
  // src/renderers/bundlers/css.ts
7267
7297
  init_errors();
7268
7298
  init_utils();
7299
+ var REF_PREFIX_SETS = "#/sets/";
7300
+ var REF_PREFIX_MODIFIERS = "#/modifiers/";
7269
7301
  var getSourceSet = (token) => {
7270
7302
  if (typeof token !== "object" || token === null) {
7271
7303
  return void 0;
7272
7304
  }
7273
- const maybe = token;
7274
- return typeof maybe._sourceSet === "string" ? maybe._sourceSet : void 0;
7305
+ return "_sourceSet" in token && typeof token._sourceSet === "string" ? token._sourceSet : void 0;
7275
7306
  };
7276
7307
  var getSourceModifier = (token) => {
7277
7308
  if (typeof token !== "object" || token === null) {
7278
7309
  return void 0;
7279
7310
  }
7280
- const maybe = token;
7281
- return typeof maybe._sourceModifier === "string" ? maybe._sourceModifier : void 0;
7311
+ return "_sourceModifier" in token && typeof token._sourceModifier === "string" ? token._sourceModifier : void 0;
7282
7312
  };
7283
7313
  async function bundleAsCss(bundleData, resolver, options, formatTokens) {
7284
7314
  const baseItem = bundleData.find((item) => item.isBase);
@@ -7363,6 +7393,15 @@ async function formatModifierPermutation({ tokens, modifierInputs }, baseItem, o
7363
7393
  return `/* Modifier: ${modifier}=${context} */
7364
7394
  ${css2}`;
7365
7395
  }
7396
+ function addLayerBlock(blocks, included, key, blockTokens, description) {
7397
+ if (Object.keys(blockTokens).length === 0) {
7398
+ return;
7399
+ }
7400
+ for (const k of Object.keys(blockTokens)) {
7401
+ included.add(k);
7402
+ }
7403
+ blocks.push({ key, description, tokens: blockTokens });
7404
+ }
7366
7405
  function collectSetTokens(tokens, setName, included) {
7367
7406
  const result = {};
7368
7407
  for (const [name, token] of Object.entries(tokens)) {
@@ -7393,75 +7432,67 @@ function collectRemainder(tokens, included) {
7393
7432
  function buildSetLayerBlocks(tokens, resolver) {
7394
7433
  const blocks = [];
7395
7434
  const included = /* @__PURE__ */ new Set();
7396
- const addBlock = (key, blockTokens, description) => {
7397
- if (Object.keys(blockTokens).length === 0) {
7398
- return;
7399
- }
7400
- for (const k of Object.keys(blockTokens)) {
7401
- included.add(k);
7402
- }
7403
- blocks.push({ key, description, tokens: blockTokens });
7404
- };
7405
7435
  for (const item of resolver.resolutionOrder) {
7406
7436
  const ref = item.$ref;
7407
- if (typeof ref !== "string" || !ref.startsWith("#/sets/")) {
7437
+ if (typeof ref !== "string" || !ref.startsWith(REF_PREFIX_SETS)) {
7408
7438
  continue;
7409
7439
  }
7410
- const setName = ref.slice("#/sets/".length);
7411
- addBlock(
7440
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7441
+ addLayerBlock(
7442
+ blocks,
7443
+ included,
7412
7444
  `Set: ${setName}`,
7413
7445
  collectSetTokens(tokens, setName, included),
7414
7446
  resolver.sets?.[setName]?.description
7415
7447
  );
7416
7448
  }
7417
- addBlock("Unattributed", collectRemainder(tokens, included));
7449
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7418
7450
  return blocks;
7419
7451
  }
7420
7452
  function buildDefaultLayerBlocks(tokens, baseModifierInputs, resolver) {
7421
7453
  const blocks = [];
7422
7454
  const included = /* @__PURE__ */ new Set();
7423
7455
  const baseInputs = normalizeModifierInputs(baseModifierInputs);
7424
- const addBlock = (key, blockTokens, description) => {
7425
- if (Object.keys(blockTokens).length === 0) {
7426
- return;
7427
- }
7428
- for (const k of Object.keys(blockTokens)) {
7429
- included.add(k);
7430
- }
7431
- blocks.push({ key, description, tokens: blockTokens });
7432
- };
7433
7456
  for (const item of resolver.resolutionOrder) {
7434
7457
  const ref = item.$ref;
7435
7458
  if (typeof ref !== "string") {
7436
7459
  continue;
7437
7460
  }
7438
- if (ref.startsWith("#/sets/")) {
7439
- const setName = ref.slice("#/sets/".length);
7440
- addBlock(
7441
- `Set: ${setName}`,
7442
- collectSetTokens(tokens, setName, included),
7443
- resolver.sets?.[setName]?.description
7444
- );
7445
- continue;
7446
- }
7447
- if (ref.startsWith("#/modifiers/")) {
7448
- const modifierName = ref.slice("#/modifiers/".length);
7449
- const modifier = resolver.modifiers?.[modifierName];
7450
- const selectedContext = baseInputs[modifierName.toLowerCase()];
7451
- if (!modifier || !selectedContext) {
7452
- continue;
7453
- }
7454
- const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7455
- addBlock(
7456
- `Modifier: ${modifierName}=${selectedContext} (default)`,
7457
- collectModifierTokens(tokens, expectedSource, included),
7458
- modifier.description
7459
- );
7460
- }
7461
+ processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver);
7461
7462
  }
7462
- addBlock("Unattributed", collectRemainder(tokens, included));
7463
+ addLayerBlock(blocks, included, "Unattributed", collectRemainder(tokens, included));
7463
7464
  return blocks;
7464
7465
  }
7466
+ function processResolutionOrderRef(ref, tokens, blocks, included, baseInputs, resolver) {
7467
+ if (ref.startsWith(REF_PREFIX_SETS)) {
7468
+ const setName = ref.slice(REF_PREFIX_SETS.length);
7469
+ addLayerBlock(
7470
+ blocks,
7471
+ included,
7472
+ `Set: ${setName}`,
7473
+ collectSetTokens(tokens, setName, included),
7474
+ resolver.sets?.[setName]?.description
7475
+ );
7476
+ return;
7477
+ }
7478
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7479
+ return;
7480
+ }
7481
+ const modifierName = ref.slice(REF_PREFIX_MODIFIERS.length);
7482
+ const modifier = resolver.modifiers?.[modifierName];
7483
+ const selectedContext = baseInputs[modifierName.toLowerCase()];
7484
+ if (!modifier || !selectedContext) {
7485
+ return;
7486
+ }
7487
+ const expectedSource = `${modifierName}-${selectedContext}`.toLowerCase();
7488
+ addLayerBlock(
7489
+ blocks,
7490
+ included,
7491
+ `Modifier: ${modifierName}=${selectedContext} (default)`,
7492
+ collectModifierTokens(tokens, expectedSource, included),
7493
+ modifier.description
7494
+ );
7495
+ }
7465
7496
  function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs) {
7466
7497
  const normalizedModifier = modifierName.toLowerCase();
7467
7498
  const normalizedContext = context.toLowerCase();
@@ -7476,35 +7507,19 @@ function findSingleDiffPermutation(bundleData, modifierName, context, baseInputs
7476
7507
  return Object.entries(baseInputs).every(([k, v]) => k === normalizedModifier || inputs[k] === v);
7477
7508
  });
7478
7509
  }
7479
- function orderBundleData(bundleData, resolver, baseItem) {
7480
- const modifiers = resolver.modifiers;
7481
- if (!modifiers) {
7482
- return bundleData;
7510
+ function pushUniqueBundleItem(ordered, includedKeys, item) {
7511
+ if (!item) {
7512
+ return;
7483
7513
  }
7484
- const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7485
- const orderedModifierNames = getOrderedModifierNames(resolver);
7486
- if (orderedModifierNames.length === 0) {
7487
- return bundleData;
7488
- }
7489
- const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7490
- if (!firstModifierDef) {
7491
- return bundleData;
7514
+ const key = stableInputsKey(item.modifierInputs);
7515
+ if (includedKeys.has(key)) {
7516
+ return;
7492
7517
  }
7493
- const includedKeys = /* @__PURE__ */ new Set();
7494
- const ordered = [];
7495
- const pushUnique = (item) => {
7496
- if (!item) {
7497
- return;
7498
- }
7499
- const key = stableInputsKey(item.modifierInputs);
7500
- if (includedKeys.has(key)) {
7501
- return;
7502
- }
7503
- includedKeys.add(key);
7504
- ordered.push(item);
7505
- };
7506
- pushUnique(baseItem);
7507
- for (const modifierName of orderedModifierNames) {
7518
+ includedKeys.add(key);
7519
+ ordered.push(item);
7520
+ }
7521
+ function appendModifierPermutations(bundleData, modifiers, orderedNames, baseInputs, ordered, includedKeys) {
7522
+ for (const modifierName of orderedNames) {
7508
7523
  const modifierDef = modifiers[modifierName];
7509
7524
  if (!modifierDef) {
7510
7525
  continue;
@@ -7514,9 +7529,39 @@ function orderBundleData(bundleData, resolver, baseItem) {
7514
7529
  if (defaultValue === ctx.toLowerCase()) {
7515
7530
  continue;
7516
7531
  }
7517
- pushUnique(findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs));
7532
+ pushUniqueBundleItem(
7533
+ ordered,
7534
+ includedKeys,
7535
+ findSingleDiffPermutation(bundleData, modifierName, ctx, baseInputs)
7536
+ );
7518
7537
  }
7519
7538
  }
7539
+ }
7540
+ function orderBundleData(bundleData, resolver, baseItem) {
7541
+ const modifiers = resolver.modifiers;
7542
+ if (!modifiers) {
7543
+ return bundleData;
7544
+ }
7545
+ const baseInputs = normalizeModifierInputs(baseItem.modifierInputs);
7546
+ const orderedModifierNames = getOrderedModifierNames(resolver);
7547
+ if (orderedModifierNames.length === 0) {
7548
+ return bundleData;
7549
+ }
7550
+ const firstModifierDef = modifiers[orderedModifierNames[0] ?? ""];
7551
+ if (!firstModifierDef) {
7552
+ return bundleData;
7553
+ }
7554
+ const includedKeys = /* @__PURE__ */ new Set();
7555
+ const ordered = [];
7556
+ pushUniqueBundleItem(ordered, includedKeys, baseItem);
7557
+ appendModifierPermutations(
7558
+ bundleData,
7559
+ modifiers,
7560
+ orderedModifierNames,
7561
+ baseInputs,
7562
+ ordered,
7563
+ includedKeys
7564
+ );
7520
7565
  return ordered.length > 0 ? ordered : bundleData;
7521
7566
  }
7522
7567
  function getOrderedModifierNames(resolver) {
@@ -7528,10 +7573,10 @@ function getOrderedModifierNames(resolver) {
7528
7573
  if (typeof ref !== "string") {
7529
7574
  continue;
7530
7575
  }
7531
- if (!ref.startsWith("#/modifiers/")) {
7576
+ if (!ref.startsWith(REF_PREFIX_MODIFIERS)) {
7532
7577
  continue;
7533
7578
  }
7534
- const name = ref.slice("#/modifiers/".length);
7579
+ const name = ref.slice(REF_PREFIX_MODIFIERS.length);
7535
7580
  if (seen.has(name)) {
7536
7581
  continue;
7537
7582
  }
@@ -7602,24 +7647,22 @@ var CssRenderer = class _CssRenderer {
7602
7647
  ...options,
7603
7648
  referenceTokens: options?.referenceTokens ?? tokens
7604
7649
  };
7605
- const groups = this.groupTokens(tokens, opts);
7650
+ const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7606
7651
  const referenceTokens = opts.referenceTokens;
7607
7652
  const lines = [];
7608
- for (const [selector, groupTokens] of Object.entries(groups)) {
7609
- this.buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts);
7610
- }
7653
+ this.buildCssBlock(lines, sortedTokens, opts.selector, tokens, referenceTokens, opts);
7611
7654
  const cssString = lines.join("");
7612
7655
  return opts.minify ? cssString : await this.formatWithPrettier(cssString);
7613
7656
  }
7614
7657
  buildCssBlock(lines, groupTokens, selector, tokens, referenceTokens, opts) {
7615
- const indent2 = opts.minify ? "" : " ";
7658
+ const indent = opts.minify ? "" : " ";
7616
7659
  const newline = opts.minify ? "" : "\n";
7617
7660
  const space = opts.minify ? "" : " ";
7618
7661
  const hasMediaQuery = opts.mediaQuery != null && opts.mediaQuery !== "";
7619
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
7662
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
7620
7663
  if (hasMediaQuery) {
7621
7664
  lines.push(`@media ${opts.mediaQuery}${space}{${newline}`);
7622
- lines.push(`${indent2}${selector}${space}{${newline}`);
7665
+ lines.push(`${indent}${selector}${space}{${newline}`);
7623
7666
  } else {
7624
7667
  lines.push(`${selector}${space}{${newline}`);
7625
7668
  }
@@ -7636,21 +7679,21 @@ var CssRenderer = class _CssRenderer {
7636
7679
  );
7637
7680
  }
7638
7681
  if (hasMediaQuery) {
7639
- lines.push(`${indent2}}${newline}`);
7682
+ lines.push(`${indent}}${newline}`);
7640
7683
  }
7641
7684
  lines.push(`}${newline}${newline}`);
7642
7685
  }
7643
- pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent2, newline, space) {
7686
+ pushTokenLines(lines, token, tokens, referenceTokens, preserveReferences, indent, newline, space) {
7644
7687
  const entries = this.buildCssEntries(token, tokens, referenceTokens, preserveReferences);
7645
7688
  if (token.$deprecated != null && token.$deprecated !== false) {
7646
7689
  const deprecationMsg = formatDeprecationMessage(token, "", "comment");
7647
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7690
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(deprecationMsg)} */${newline}`);
7648
7691
  }
7649
7692
  if (token.$description && token.$description !== "") {
7650
- lines.push(`${indent2}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7693
+ lines.push(`${indent}/* ${this.sanitizeCssCommentText(token.$description)} */${newline}`);
7651
7694
  }
7652
7695
  for (const entry of entries) {
7653
- lines.push(`${indent2}--${entry.name}:${space}${entry.value};${newline}`);
7696
+ lines.push(`${indent}--${entry.name}:${space}${entry.value};${newline}`);
7654
7697
  }
7655
7698
  }
7656
7699
  async formatWithPrettier(css2) {
@@ -7665,15 +7708,6 @@ var CssRenderer = class _CssRenderer {
7665
7708
  return css2;
7666
7709
  }
7667
7710
  }
7668
- /**
7669
- * Group tokens by selector (for theme support)
7670
- */
7671
- groupTokens(tokens, options) {
7672
- const sortedTokens = getSortedTokenEntries(tokens).map(([, token]) => token);
7673
- return {
7674
- [options.selector]: sortedTokens
7675
- };
7676
- }
7677
7711
  buildCssEntries(token, tokens, referenceTokens, preserveReferences) {
7678
7712
  if (preserveReferences) {
7679
7713
  const refName = getPureAliasReferenceName(token.originalValue);
@@ -7815,7 +7849,7 @@ var CssRenderer = class _CssRenderer {
7815
7849
  leaves.push({ path: path7, value });
7816
7850
  return;
7817
7851
  }
7818
- if (isColorObject(value) || isDimensionObject(value) || this.isDurationObject(value)) {
7852
+ if (isColorObject(value) || isDimensionObject(value) || isDurationObject(value)) {
7819
7853
  leaves.push({ path: path7, value });
7820
7854
  return;
7821
7855
  }
@@ -7858,8 +7892,8 @@ var CssRenderer = class _CssRenderer {
7858
7892
  if (isDimensionObject(value)) {
7859
7893
  return dimensionObjectToString(value);
7860
7894
  }
7861
- if (this.isDurationObject(value)) {
7862
- return this.formatDurationValue(value);
7895
+ if (isDurationObject(value)) {
7896
+ return durationObjectToString(value);
7863
7897
  }
7864
7898
  if (typeof value === "string") {
7865
7899
  return value;
@@ -7922,15 +7956,6 @@ var CssRenderer = class _CssRenderer {
7922
7956
  isPrimitiveValue(value) {
7923
7957
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
7924
7958
  }
7925
- isDurationObject(value) {
7926
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
7927
- }
7928
- formatDurationValue(value) {
7929
- if (typeof value === "string") {
7930
- return value;
7931
- }
7932
- return `${value.value}${value.unit}`;
7933
- }
7934
7959
  /**
7935
7960
  * Format token value for CSS
7936
7961
  * Handles DTCG 2025.10 object formats for colors and dimensions
@@ -7951,8 +7976,8 @@ var CssRenderer = class _CssRenderer {
7951
7976
  return typeof value === "string" ? value : dimensionObjectToString(value);
7952
7977
  }
7953
7978
  if (type === "duration") {
7954
- if (this.isDurationObject(value)) {
7955
- return this.formatDurationValue(value);
7979
+ if (isDurationObject(value)) {
7980
+ return durationObjectToString(value);
7956
7981
  }
7957
7982
  if (typeof value === "string") {
7958
7983
  return value;
@@ -8042,16 +8067,16 @@ var CssRenderer = class _CssRenderer {
8042
8067
  */
8043
8068
  formatTransition(value) {
8044
8069
  const parts = [];
8045
- if (this.isDurationObject(value.duration)) {
8046
- parts.push(this.formatDurationValue(value.duration));
8070
+ if (isDurationObject(value.duration)) {
8071
+ parts.push(durationObjectToString(value.duration));
8047
8072
  } else if (value.duration != null) {
8048
8073
  parts.push(String(value.duration));
8049
8074
  }
8050
8075
  if (Array.isArray(value.timingFunction) && value.timingFunction.length === 4) {
8051
8076
  parts.push(`cubic-bezier(${value.timingFunction.join(", ")})`);
8052
8077
  }
8053
- if (this.isDurationObject(value.delay)) {
8054
- parts.push(this.formatDurationValue(value.delay));
8078
+ if (isDurationObject(value.delay)) {
8079
+ parts.push(durationObjectToString(value.delay));
8055
8080
  } else if (value.delay != null) {
8056
8081
  parts.push(String(value.delay));
8057
8082
  }
@@ -8061,7 +8086,7 @@ var CssRenderer = class _CssRenderer {
8061
8086
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8062
8087
  tokens,
8063
8088
  modifierInputs,
8064
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8089
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8065
8090
  }));
8066
8091
  return await bundleAsCss(bundleData, context.resolver, options, async (tokens, resolved) => {
8067
8092
  return await this.formatTokens(tokens, {
@@ -8071,12 +8096,12 @@ var CssRenderer = class _CssRenderer {
8071
8096
  });
8072
8097
  }
8073
8098
  async formatStandalone(context, options) {
8074
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8075
- if (!context.output.file && requiresFile) {
8076
- throw new ConfigurationError(
8077
- `Output "${context.output.name}": file is required for standalone CSS output`
8078
- );
8079
- }
8099
+ assertFileRequired(
8100
+ context.buildPath,
8101
+ context.output.file,
8102
+ context.output.name,
8103
+ "standalone CSS"
8104
+ );
8080
8105
  const files = {};
8081
8106
  for (const { tokens, modifierInputs } of context.permutations) {
8082
8107
  const { fileName, content } = await this.buildStandaloneFile(
@@ -8090,7 +8115,7 @@ var CssRenderer = class _CssRenderer {
8090
8115
  return { kind: "outputTree", files };
8091
8116
  }
8092
8117
  async buildStandaloneFile(tokens, modifierInputs, context, options) {
8093
- const isBase = this.isBasePermutation(modifierInputs, context.meta.defaults);
8118
+ const isBase = isBasePermutation(modifierInputs, context.meta.defaults);
8094
8119
  const { modifierName, modifierContext } = this.resolveModifierContext(
8095
8120
  modifierInputs,
8096
8121
  context,
@@ -8127,12 +8152,7 @@ var CssRenderer = class _CssRenderer {
8127
8152
  return { fileName, content };
8128
8153
  }
8129
8154
  async formatModifier(context, options) {
8130
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8131
- if (!context.output.file && requiresFile) {
8132
- throw new ConfigurationError(
8133
- `Output "${context.output.name}": file is required for modifier CSS output`
8134
- );
8135
- }
8155
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "modifier CSS");
8136
8156
  if (!context.resolver.modifiers) {
8137
8157
  throw new ConfigurationError("Modifier preset requires modifiers to be defined in resolver");
8138
8158
  }
@@ -8158,7 +8178,7 @@ var CssRenderer = class _CssRenderer {
8158
8178
  }
8159
8179
  async buildModifierBaseFile(context, options) {
8160
8180
  const basePermutation = context.permutations.find(
8161
- ({ modifierInputs }) => this.isBasePermutation(modifierInputs, context.meta.defaults)
8181
+ ({ modifierInputs }) => isBasePermutation(modifierInputs, context.meta.defaults)
8162
8182
  );
8163
8183
  if (!basePermutation) {
8164
8184
  return void 0;
@@ -8171,25 +8191,40 @@ var CssRenderer = class _CssRenderer {
8171
8191
  if (setBlocks.length === 0) {
8172
8192
  return void 0;
8173
8193
  }
8194
+ const { selector, mediaQuery } = this.resolveBaseModifierContext(context, options);
8195
+ const content = await this.formatSetBlocksCss(
8196
+ setBlocks,
8197
+ basePermutation.tokens,
8198
+ selector,
8199
+ mediaQuery,
8200
+ options
8201
+ );
8202
+ const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8203
+ return { fileName, content };
8204
+ }
8205
+ resolveBaseModifierContext(context, options) {
8174
8206
  const modifiers = context.resolver.modifiers;
8175
8207
  const firstModifierName = Object.keys(modifiers)[0] ?? "";
8176
8208
  const firstModifierContext = context.meta.defaults[firstModifierName] ?? "";
8177
8209
  const baseModifierInputs = { ...context.meta.defaults };
8178
- const selector = resolveSelector(
8179
- options.selector,
8180
- firstModifierName,
8181
- firstModifierContext,
8182
- true,
8183
- baseModifierInputs
8184
- );
8185
- const mediaQuery = resolveMediaQuery(
8186
- options.mediaQuery,
8187
- firstModifierName,
8188
- firstModifierContext,
8189
- true,
8190
- baseModifierInputs
8191
- );
8192
- const referenceTokens = basePermutation.tokens;
8210
+ return {
8211
+ selector: resolveSelector(
8212
+ options.selector,
8213
+ firstModifierName,
8214
+ firstModifierContext,
8215
+ true,
8216
+ baseModifierInputs
8217
+ ),
8218
+ mediaQuery: resolveMediaQuery(
8219
+ options.mediaQuery,
8220
+ firstModifierName,
8221
+ firstModifierContext,
8222
+ true,
8223
+ baseModifierInputs
8224
+ )
8225
+ };
8226
+ }
8227
+ async formatSetBlocksCss(setBlocks, referenceTokens, selector, mediaQuery, options) {
8193
8228
  const cssBlocks = [];
8194
8229
  for (const block of setBlocks) {
8195
8230
  const cleanTokens = stripInternalMetadata(block.tokens);
@@ -8205,9 +8240,7 @@ var CssRenderer = class _CssRenderer {
8205
8240
  cssBlocks.push(`${header}
8206
8241
  ${css2}`);
8207
8242
  }
8208
- const content = cssBlocks.join("\n");
8209
- const fileName = context.output.file ? resolveBaseFileName(context.output.file, context.meta.defaults) : `${context.output.name}-base.css`;
8210
- return { fileName, content };
8243
+ return cssBlocks.join("\n");
8211
8244
  }
8212
8245
  collectTokensForModifierContext(modifierName, contextValue, permutations) {
8213
8246
  const expectedSource = `${modifierName}-${contextValue}`;
@@ -8284,13 +8317,6 @@ ${css2}`);
8284
8317
  }
8285
8318
  return { modifierName: "", modifierContext: "" };
8286
8319
  }
8287
- isBasePermutation(modifierInputs, defaults) {
8288
- const normalizedInputs = normalizeModifierInputs(modifierInputs);
8289
- const normalizedDefaults = normalizeModifierInputs(defaults);
8290
- return Object.entries(normalizedDefaults).every(
8291
- ([key, value]) => normalizedInputs[key] === value
8292
- );
8293
- }
8294
8320
  };
8295
8321
  function cssRenderer() {
8296
8322
  const rendererInstance = new CssRenderer();
@@ -8303,8 +8329,6 @@ function cssRenderer() {
8303
8329
  }
8304
8330
 
8305
8331
  // src/renderers/ios.ts
8306
- init_errors();
8307
- init_token_utils();
8308
8332
  init_utils();
8309
8333
  var toSRGB2 = converter("rgb");
8310
8334
  var toP32 = converter("p3");
@@ -8393,94 +8417,68 @@ var IosRenderer = class {
8393
8417
  return await this.formatStandalone(context, opts);
8394
8418
  }
8395
8419
  formatTokens(tokens, options) {
8396
- if (options.structure === "grouped") {
8397
- return this.formatAsGrouped(tokens, options);
8398
- }
8399
- return this.formatAsEnum(tokens, options);
8400
- }
8401
- formatAsEnum(tokens, options) {
8402
8420
  const access3 = options.accessLevel;
8403
- const groups = this.groupTokensByType(tokens);
8421
+ const groups = groupTokensByType(tokens, SWIFT_TYPE_GROUP_MAP);
8404
8422
  const imports = this.collectImports(tokens);
8405
- const i1 = this.indentStr(options.indent, 1);
8406
- const i2 = this.indentStr(options.indent, 2);
8407
8423
  const staticPrefix = this.staticLetPrefix(options);
8408
8424
  const frozen = this.frozenPrefix(options);
8409
8425
  const lines = [];
8410
- lines.push(this.buildFileHeader());
8426
+ lines.push(buildGeneratedFileHeader());
8411
8427
  lines.push("");
8412
8428
  for (const imp of imports) {
8413
8429
  lines.push(`import ${imp}`);
8414
8430
  }
8415
8431
  lines.push(...this.buildStructDefinitions(tokens, access3, options));
8432
+ this.pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen);
8433
+ lines.push(...this.buildViewExtensions(tokens, access3, options));
8434
+ if (options.structure !== "grouped") {
8435
+ lines.push("");
8436
+ }
8437
+ return lines.join("\n");
8438
+ }
8439
+ pushTokenLayout(lines, groups, options, access3, staticPrefix, frozen) {
8440
+ const i1 = indentStr(options.indent, 1);
8441
+ const i2 = indentStr(options.indent, 2);
8442
+ if (options.structure === "grouped") {
8443
+ this.pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen);
8444
+ return;
8445
+ }
8416
8446
  lines.push("");
8417
8447
  lines.push(`${frozen}${access3} enum ${options.enumName} {`);
8418
8448
  for (const group of groups) {
8419
8449
  lines.push(`${i1}${frozen}${access3} enum ${group.name} {`);
8420
- for (const token of group.tokens) {
8421
- const swiftName = this.buildQualifiedSwiftName(token);
8422
- const swiftValue = this.formatSwiftValue(token, options);
8423
- const typeAnnotation = this.getTypeAnnotation(token);
8424
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8425
- const docComment = this.buildDocComment(token, i2);
8426
- if (docComment) {
8427
- lines.push(docComment);
8428
- }
8429
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8430
- }
8450
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8431
8451
  lines.push(`${i1}}`);
8432
8452
  lines.push("");
8433
8453
  }
8434
8454
  lines.push("}");
8435
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8436
- lines.push("");
8437
- return lines.join("\n");
8438
8455
  }
8439
- formatAsGrouped(tokens, options) {
8440
- const access3 = options.accessLevel;
8456
+ pushGroupedLayout(lines, groups, options, access3, i1, i2, staticPrefix, frozen) {
8441
8457
  const namespace = options.extensionNamespace;
8442
- const groups = this.groupTokensByType(tokens);
8443
- const imports = this.collectImports(tokens);
8444
- const i1 = this.indentStr(options.indent, 1);
8445
- const i2 = this.indentStr(options.indent, 2);
8446
- const staticPrefix = this.staticLetPrefix(options);
8447
- const frozen = this.frozenPrefix(options);
8448
- const lines = [];
8449
- lines.push(this.buildFileHeader());
8450
- lines.push("");
8451
- for (const imp of imports) {
8452
- lines.push(`import ${imp}`);
8453
- }
8454
- lines.push(...this.buildStructDefinitions(tokens, access3, options));
8455
8458
  lines.push("");
8456
8459
  lines.push(`${frozen}${access3} enum ${namespace} {}`);
8457
8460
  lines.push("");
8458
8461
  for (const group of groups) {
8459
8462
  lines.push(`${access3} extension ${namespace} {`);
8460
8463
  lines.push(`${i1}${frozen}enum ${group.name} {`);
8461
- for (const token of group.tokens) {
8462
- const swiftName = this.buildQualifiedSwiftName(token);
8463
- const swiftValue = this.formatSwiftValue(token, options);
8464
- const typeAnnotation = this.getTypeAnnotation(token);
8465
- const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8466
- const docComment = this.buildDocComment(token, i2);
8467
- if (docComment) {
8468
- lines.push(docComment);
8469
- }
8470
- lines.push(`${i2}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8471
- }
8464
+ this.pushTokenDeclarations(lines, group.tokens, options, access3, i2, staticPrefix);
8472
8465
  lines.push(`${i1}}`);
8473
8466
  lines.push("}");
8474
8467
  lines.push("");
8475
8468
  }
8476
- lines.push(...this.buildViewExtensions(tokens, access3, options));
8477
- return lines.join("\n");
8478
8469
  }
8479
- buildFileHeader() {
8480
- return [
8481
- "// Generated by Dispersa - do not edit manually",
8482
- "// https://github.com/timges/dispersa"
8483
- ].join("\n");
8470
+ pushTokenDeclarations(lines, tokens, options, access3, indent, staticPrefix) {
8471
+ for (const token of tokens) {
8472
+ const swiftName = this.buildQualifiedSwiftName(token);
8473
+ const swiftValue = this.formatSwiftValue(token, options);
8474
+ const typeAnnotation = this.getTypeAnnotation(token);
8475
+ const annotation = typeAnnotation ? `: ${typeAnnotation}` : "";
8476
+ const docComment = this.buildDocComment(token, indent);
8477
+ if (docComment) {
8478
+ lines.push(docComment);
8479
+ }
8480
+ lines.push(`${indent}${access3} ${staticPrefix}${swiftName}${annotation} = ${swiftValue}`);
8481
+ }
8484
8482
  }
8485
8483
  collectImports(tokens) {
8486
8484
  const imports = /* @__PURE__ */ new Set();
@@ -8495,24 +8493,11 @@ var IosRenderer = class {
8495
8493
  /**
8496
8494
  * Builds a `///` doc comment from a token's `$description`, if present.
8497
8495
  */
8498
- buildDocComment(token, indent2) {
8496
+ buildDocComment(token, indent) {
8499
8497
  if (!token.$description) {
8500
8498
  return void 0;
8501
8499
  }
8502
- return `${indent2}/// ${token.$description}`;
8503
- }
8504
- groupTokensByType(tokens) {
8505
- const groupMap = /* @__PURE__ */ new Map();
8506
- for (const [, token] of getSortedTokenEntries(tokens)) {
8507
- const groupName = SWIFT_TYPE_GROUP_MAP[token.$type ?? ""] ?? "Other";
8508
- const existing = groupMap.get(groupName) ?? [];
8509
- existing.push(token);
8510
- groupMap.set(groupName, existing);
8511
- }
8512
- return Array.from(groupMap.entries()).map(([name, groupTokens]) => ({
8513
- name,
8514
- tokens: groupTokens
8515
- }));
8500
+ return `${indent}/// ${token.$description}`;
8516
8501
  }
8517
8502
  /**
8518
8503
  * Builds a qualified Swift name from a token's path, preserving parent
@@ -8525,43 +8510,40 @@ var IosRenderer = class {
8525
8510
  const path7 = token.path;
8526
8511
  const withoutTypePrefix = path7.length > 1 ? path7.slice(1) : path7;
8527
8512
  const joined = withoutTypePrefix.join("_");
8528
- return this.toSwiftIdentifier(joined);
8513
+ return toSafeIdentifier(joined, SWIFT_KEYWORDS, false);
8529
8514
  }
8530
8515
  formatSwiftValue(token, options) {
8531
- const value = token.$value;
8532
- if (token.$type === "color") {
8533
- return this.formatColorValue(value, options);
8534
- }
8535
- if (token.$type === "dimension") {
8536
- return this.formatDimensionValue(value);
8537
- }
8538
- if (token.$type === "fontFamily") {
8539
- return this.formatFontFamilyValue(value);
8540
- }
8541
- if (token.$type === "fontWeight") {
8542
- return this.formatFontWeightValue(value);
8543
- }
8544
- if (token.$type === "duration") {
8545
- return this.formatDurationValue(value);
8546
- }
8547
- if (token.$type === "shadow") {
8548
- return this.formatShadowValue(value, options);
8549
- }
8550
- if (token.$type === "typography") {
8551
- return this.formatTypographyValue(value);
8552
- }
8553
- if (token.$type === "border") {
8554
- return this.formatBorderValue(value, options);
8555
- }
8556
- if (token.$type === "gradient") {
8557
- return this.formatGradientValue(value, options);
8558
- }
8559
- if (token.$type === "number") {
8560
- return String(value);
8561
- }
8562
- if (token.$type === "cubicBezier" && Array.isArray(value) && value.length === 4) {
8563
- return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8516
+ const { $type, $value: value } = token;
8517
+ switch ($type) {
8518
+ case "color":
8519
+ return this.formatColorValue(value, options);
8520
+ case "dimension":
8521
+ return this.formatDimensionValue(value);
8522
+ case "fontFamily":
8523
+ return this.formatFontFamilyValue(value);
8524
+ case "fontWeight":
8525
+ return this.formatFontWeightValue(value);
8526
+ case "duration":
8527
+ return this.formatDurationValue(value);
8528
+ case "shadow":
8529
+ return this.formatShadowValue(value, options);
8530
+ case "typography":
8531
+ return this.formatTypographyValue(value);
8532
+ case "border":
8533
+ return this.formatBorderValue(value, options);
8534
+ case "gradient":
8535
+ return this.formatGradientValue(value, options);
8536
+ case "number":
8537
+ return String(value);
8538
+ case "cubicBezier":
8539
+ if (Array.isArray(value) && value.length === 4) {
8540
+ return `UnitCurve.bezier(startControlPoint: UnitPoint(x: ${value[0]}, y: ${value[1]}), endControlPoint: UnitPoint(x: ${value[2]}, y: ${value[3]}))`;
8541
+ }
8542
+ break;
8564
8543
  }
8544
+ return this.formatSwiftPrimitive(value);
8545
+ }
8546
+ formatSwiftPrimitive(value) {
8565
8547
  if (typeof value === "string") {
8566
8548
  return `"${this.escapeSwiftString(value)}"`;
8567
8549
  }
@@ -8594,9 +8576,7 @@ var IosRenderer = class {
8594
8576
  }
8595
8577
  formatDimensionValue(value) {
8596
8578
  if (isDimensionObject(value)) {
8597
- const dim = value;
8598
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8599
- return String(ptValue);
8579
+ return this.dimensionToPoints(value);
8600
8580
  }
8601
8581
  return String(value);
8602
8582
  }
@@ -8663,7 +8643,7 @@ var IosRenderer = class {
8663
8643
  return map[name.toLowerCase()];
8664
8644
  }
8665
8645
  formatDurationValue(value) {
8666
- if (typeof value === "object" && value !== null && "value" in value && "unit" in value) {
8646
+ if (isDurationObject(value)) {
8667
8647
  const dur = value;
8668
8648
  const seconds = dur.unit === "ms" ? dur.value / 1e3 : dur.value;
8669
8649
  return String(seconds);
@@ -8712,9 +8692,7 @@ var IosRenderer = class {
8712
8692
  if (!isDimensionObject(typo.letterSpacing)) {
8713
8693
  return "0";
8714
8694
  }
8715
- const dim = typo.letterSpacing;
8716
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8717
- return String(ptValue);
8695
+ return this.dimensionToPoints(typo.letterSpacing);
8718
8696
  }
8719
8697
  extractLineSpacing(typo) {
8720
8698
  if (typo.lineHeight == null || typeof typo.lineHeight !== "number") {
@@ -8723,18 +8701,19 @@ var IosRenderer = class {
8723
8701
  if (!isDimensionObject(typo.fontSize)) {
8724
8702
  return "0";
8725
8703
  }
8726
- const dim = typo.fontSize;
8727
- const basePt = dim.unit === "rem" ? dim.value * 16 : dim.value;
8704
+ const basePt = this.dimensionToNumericPoints(typo.fontSize);
8728
8705
  const lineHeightPt = Math.round(basePt * typo.lineHeight * 100) / 100;
8729
8706
  return String(lineHeightPt - basePt);
8730
8707
  }
8708
+ dimensionToNumericPoints(dim) {
8709
+ return dim.unit === "rem" ? dim.value * 16 : dim.value;
8710
+ }
8731
8711
  dimensionToPoints(dim) {
8732
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8733
- return String(ptValue);
8712
+ return String(this.dimensionToNumericPoints(dim));
8734
8713
  }
8735
8714
  /** Formats a dimension as a CGFloat literal (appends `.0` for integers). */
8736
8715
  dimensionToCGFloat(dim) {
8737
- const ptValue = dim.unit === "rem" ? dim.value * 16 : dim.value;
8716
+ const ptValue = this.dimensionToNumericPoints(dim);
8738
8717
  return Number.isInteger(ptValue) ? `${ptValue}.0` : String(ptValue);
8739
8718
  }
8740
8719
  getTypeAnnotation(token) {
@@ -8753,21 +8732,12 @@ var IosRenderer = class {
8753
8732
  return void 0;
8754
8733
  }
8755
8734
  }
8756
- toSwiftIdentifier(name) {
8757
- const camel = name.replace(/[-._]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-._]+$/g, "").replace(/^[-._]+/g, "");
8758
- const identifier = camel.charAt(0).toLowerCase() + camel.slice(1);
8759
- const safe = /^\d/.test(identifier) ? `_${identifier}` : identifier;
8760
- return SWIFT_KEYWORDS.has(safe) ? `\`${safe}\`` : safe;
8761
- }
8762
8735
  escapeSwiftString(str) {
8763
8736
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
8764
8737
  }
8765
8738
  roundComponent(value) {
8766
8739
  return Math.round(value * 1e4) / 1e4;
8767
8740
  }
8768
- indentStr(width, level) {
8769
- return " ".repeat(width * level);
8770
- }
8771
8741
  /**
8772
8742
  * Returns the prefix for `static let` declarations.
8773
8743
  * Swift 6 requires `nonisolated(unsafe)` on global stored properties.
@@ -8783,34 +8753,25 @@ var IosRenderer = class {
8783
8753
  structConformances(options) {
8784
8754
  return options.swiftVersion === "6.0" ? ": Sendable" : "";
8785
8755
  }
8786
- hasShadowTokens(tokens) {
8787
- return Object.values(tokens).some((t) => t.$type === "shadow");
8788
- }
8789
- hasTypographyTokens(tokens) {
8790
- return Object.values(tokens).some((t) => t.$type === "typography");
8791
- }
8792
- hasBorderTokens(tokens) {
8793
- return Object.values(tokens).some((t) => t.$type === "border");
8794
- }
8795
8756
  /** Emits all struct definitions needed by the token set. */
8796
8757
  buildStructDefinitions(tokens, access3, options) {
8797
8758
  const lines = [];
8798
- if (this.hasShadowTokens(tokens)) {
8759
+ if (Object.values(tokens).some(isShadowToken)) {
8799
8760
  lines.push("");
8800
8761
  lines.push(...this.buildShadowStyleStruct(access3, options));
8801
8762
  }
8802
- if (this.hasTypographyTokens(tokens)) {
8763
+ if (Object.values(tokens).some(isTypographyToken)) {
8803
8764
  lines.push("");
8804
8765
  lines.push(...this.buildTypographyStyleStruct(access3, options));
8805
8766
  }
8806
- if (this.hasBorderTokens(tokens)) {
8767
+ if (Object.values(tokens).some(isBorderToken)) {
8807
8768
  lines.push("");
8808
8769
  lines.push(...this.buildBorderStyleStruct(access3, options));
8809
8770
  }
8810
8771
  return lines;
8811
8772
  }
8812
8773
  buildShadowStyleStruct(access3, options) {
8813
- const i1 = this.indentStr(options.indent, 1);
8774
+ const i1 = indentStr(options.indent, 1);
8814
8775
  const conformances = this.structConformances(options);
8815
8776
  const frozen = this.frozenPrefix(options);
8816
8777
  return [
@@ -8824,7 +8785,7 @@ var IosRenderer = class {
8824
8785
  ];
8825
8786
  }
8826
8787
  buildTypographyStyleStruct(access3, options) {
8827
- const i1 = this.indentStr(options.indent, 1);
8788
+ const i1 = indentStr(options.indent, 1);
8828
8789
  const conformances = this.structConformances(options);
8829
8790
  const frozen = this.frozenPrefix(options);
8830
8791
  return [
@@ -8836,7 +8797,7 @@ var IosRenderer = class {
8836
8797
  ];
8837
8798
  }
8838
8799
  buildBorderStyleStruct(access3, options) {
8839
- const i1 = this.indentStr(options.indent, 1);
8800
+ const i1 = indentStr(options.indent, 1);
8840
8801
  const conformances = this.structConformances(options);
8841
8802
  const frozen = this.frozenPrefix(options);
8842
8803
  return [
@@ -8849,9 +8810,9 @@ var IosRenderer = class {
8849
8810
  /** Emits convenience View extensions for shadow and typography application. */
8850
8811
  buildViewExtensions(tokens, access3, options) {
8851
8812
  const lines = [];
8852
- const i1 = this.indentStr(options.indent, 1);
8853
- const i2 = this.indentStr(options.indent, 2);
8854
- if (this.hasShadowTokens(tokens)) {
8813
+ const i1 = indentStr(options.indent, 1);
8814
+ const i2 = indentStr(options.indent, 2);
8815
+ if (Object.values(tokens).some(isShadowToken)) {
8855
8816
  lines.push("");
8856
8817
  lines.push(`${access3} extension View {`);
8857
8818
  lines.push(`${i1}func shadowStyle(_ style: ShadowStyle) -> some View {`);
@@ -8861,7 +8822,7 @@ var IosRenderer = class {
8861
8822
  lines.push(`${i1}}`);
8862
8823
  lines.push("}");
8863
8824
  }
8864
- if (this.hasTypographyTokens(tokens)) {
8825
+ if (Object.values(tokens).some(isTypographyToken)) {
8865
8826
  lines.push("");
8866
8827
  lines.push(`${access3} extension View {`);
8867
8828
  lines.push(`${i1}func typographyStyle(_ style: TypographyStyle) -> some View {`);
@@ -8893,12 +8854,12 @@ var IosRenderer = class {
8893
8854
  return `Gradient(stops: [${stops.join(", ")}])`;
8894
8855
  }
8895
8856
  async formatStandalone(context, options) {
8896
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8897
- if (!context.output.file && requiresFile) {
8898
- throw new ConfigurationError(
8899
- `Output "${context.output.name}": file is required for standalone iOS output`
8900
- );
8901
- }
8857
+ assertFileRequired(
8858
+ context.buildPath,
8859
+ context.output.file,
8860
+ context.output.name,
8861
+ "standalone iOS"
8862
+ );
8902
8863
  const files = {};
8903
8864
  for (const { tokens, modifierInputs } of context.permutations) {
8904
8865
  const processedTokens = stripInternalMetadata(tokens);
@@ -8927,7 +8888,6 @@ function iosRenderer() {
8927
8888
 
8928
8889
  // src/renderers/js-module.ts
8929
8890
  init_utils();
8930
- init_errors();
8931
8891
  init_token_utils();
8932
8892
  var JsModuleRenderer = class {
8933
8893
  async format(context, options) {
@@ -8943,18 +8903,13 @@ var JsModuleRenderer = class {
8943
8903
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
8944
8904
  tokens: stripInternalMetadata(tokens),
8945
8905
  modifierInputs,
8946
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
8906
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
8947
8907
  }));
8948
8908
  return await bundleAsJsModule2(bundleData, context.resolver, opts, async (tokens) => {
8949
8909
  return await this.formatTokens(tokens, opts);
8950
8910
  });
8951
8911
  }
8952
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
8953
- if (!context.output.file && requiresFile) {
8954
- throw new ConfigurationError(
8955
- `Output "${context.output.name}": file is required for JS module output`
8956
- );
8957
- }
8912
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JS module");
8958
8913
  const files = {};
8959
8914
  for (const { tokens, modifierInputs } of context.permutations) {
8960
8915
  const cleanTokens = stripInternalMetadata(tokens);
@@ -9008,42 +8963,18 @@ var JsModuleRenderer = class {
9008
8963
  lines.push(`export default ${varName}`);
9009
8964
  return lines;
9010
8965
  }
9011
- /**
9012
- * Convert tokens to plain object with flat or nested structure
9013
- */
9014
8966
  tokensToPlainObject(tokens, structure) {
8967
+ if (structure === "nested") {
8968
+ return buildNestedTokenObject(tokens, (token) => token.$value);
8969
+ }
9015
8970
  const result = {};
9016
- if (structure === "flat") {
9017
- for (const [name, token] of getSortedTokenEntries(tokens)) {
9018
- result[name] = token.$value;
9019
- }
9020
- } else {
9021
- for (const [, token] of getSortedTokenEntries(tokens)) {
9022
- const parts = token.path;
9023
- let current = result;
9024
- for (let i = 0; i < parts.length - 1; i++) {
9025
- const part = parts[i];
9026
- if (part == null) {
9027
- continue;
9028
- }
9029
- if (!(part in current)) {
9030
- current[part] = {};
9031
- }
9032
- current = current[part];
9033
- }
9034
- const lastPart = parts[parts.length - 1];
9035
- if (lastPart != null) {
9036
- current[lastPart] = token.$value;
9037
- }
9038
- }
8971
+ for (const [name, token] of getSortedTokenEntries(tokens)) {
8972
+ result[name] = token.$value;
9039
8973
  }
9040
8974
  return result;
9041
8975
  }
9042
- /**
9043
- * Add object properties to lines
9044
- */
9045
- addObjectProperties(lines, obj, indent2) {
9046
- const indentStr = " ".repeat(indent2);
8976
+ addObjectProperties(lines, obj, indent) {
8977
+ const indentStr2 = " ".repeat(indent);
9047
8978
  const entries = Object.entries(obj).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
9048
8979
  for (let i = 0; i < entries.length; i++) {
9049
8980
  const entry = entries[i];
@@ -9052,14 +8983,16 @@ var JsModuleRenderer = class {
9052
8983
  }
9053
8984
  const [key, value] = entry;
9054
8985
  const isLast = i === entries.length - 1;
9055
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
9056
- lines.push(`${indentStr}${this.quoteKey(key)}: {`);
9057
- this.addObjectProperties(lines, value, indent2 + 1);
9058
- lines.push(`${indentStr}}${isLast ? "" : ","}`);
9059
- } else {
9060
- const valueStr = JSON.stringify(value);
9061
- lines.push(`${indentStr}${this.quoteKey(key)}: ${valueStr}${isLast ? "" : ","}`);
8986
+ const isNestedObject = typeof value === "object" && value !== null && !Array.isArray(value);
8987
+ if (!isNestedObject) {
8988
+ lines.push(
8989
+ `${indentStr2}${this.quoteKey(key)}: ${JSON.stringify(value)}${isLast ? "" : ","}`
8990
+ );
8991
+ continue;
9062
8992
  }
8993
+ lines.push(`${indentStr2}${this.quoteKey(key)}: {`);
8994
+ this.addObjectProperties(lines, value, indent + 1);
8995
+ lines.push(`${indentStr2}}${isLast ? "" : ","}`);
9063
8996
  }
9064
8997
  }
9065
8998
  /**
@@ -9071,9 +9004,6 @@ var JsModuleRenderer = class {
9071
9004
  }
9072
9005
  return `"${key}"`;
9073
9006
  }
9074
- isBasePermutation(modifierInputs, defaults) {
9075
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9076
- }
9077
9007
  };
9078
9008
  function jsRenderer() {
9079
9009
  const rendererInstance = new JsModuleRenderer();
@@ -9087,7 +9017,6 @@ function jsRenderer() {
9087
9017
 
9088
9018
  // src/renderers/json.ts
9089
9019
  init_utils();
9090
- init_errors();
9091
9020
  init_token_utils();
9092
9021
  var JsonRenderer = class {
9093
9022
  async format(context, options) {
@@ -9102,18 +9031,13 @@ var JsonRenderer = class {
9102
9031
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9103
9032
  tokens: stripInternalMetadata(tokens),
9104
9033
  modifierInputs,
9105
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9034
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9106
9035
  }));
9107
9036
  return await bundleAsJson2(bundleData, context.resolver, async (tokens) => {
9108
9037
  return await this.formatTokens(tokens, opts);
9109
9038
  });
9110
9039
  }
9111
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9112
- if (!context.output.file && requiresFile) {
9113
- throw new ConfigurationError(
9114
- `Output "${context.output.name}": file is required for JSON output`
9115
- );
9116
- }
9040
+ assertFileRequired(context.buildPath, context.output.file, context.output.name, "JSON");
9117
9041
  const files = {};
9118
9042
  for (const { tokens, modifierInputs } of context.permutations) {
9119
9043
  const processedTokens = stripInternalMetadata(tokens);
@@ -9173,55 +9097,11 @@ var JsonRenderer = class {
9173
9097
  }
9174
9098
  return result;
9175
9099
  }
9176
- /**
9177
- * Nest tokens by path (values only)
9178
- */
9179
9100
  nestValues(tokens) {
9180
- const result = {};
9181
- for (const [, token] of getSortedTokenEntries(tokens)) {
9182
- const parts = token.path;
9183
- let current = result;
9184
- for (let i = 0; i < parts.length - 1; i++) {
9185
- const part = parts[i];
9186
- if (part === null || part === void 0) {
9187
- continue;
9188
- }
9189
- if (!(part in current)) {
9190
- current[part] = {};
9191
- }
9192
- current = current[part];
9193
- }
9194
- const lastPart = parts[parts.length - 1];
9195
- if (lastPart !== null && lastPart !== void 0) {
9196
- current[lastPart] = token.$value;
9197
- }
9198
- }
9199
- return result;
9101
+ return buildNestedTokenObject(tokens, (token) => token.$value);
9200
9102
  }
9201
- /**
9202
- * Nest tokens by path (with metadata)
9203
- */
9204
9103
  nestTokens(tokens) {
9205
- const result = {};
9206
- for (const [, token] of getSortedTokenEntries(tokens)) {
9207
- const parts = token.path;
9208
- let current = result;
9209
- for (let i = 0; i < parts.length - 1; i++) {
9210
- const part = parts[i];
9211
- if (part === null || part === void 0) {
9212
- continue;
9213
- }
9214
- if (!(part in current)) {
9215
- current[part] = {};
9216
- }
9217
- current = current[part];
9218
- }
9219
- const lastPart = parts[parts.length - 1];
9220
- if (lastPart !== null && lastPart !== void 0) {
9221
- current[lastPart] = this.serializeToken(token);
9222
- }
9223
- }
9224
- return result;
9104
+ return buildNestedTokenObject(tokens, (token) => this.serializeToken(token));
9225
9105
  }
9226
9106
  serializeToken(token) {
9227
9107
  return {
@@ -9232,9 +9112,6 @@ var JsonRenderer = class {
9232
9112
  ...token.$extensions != null && { $extensions: token.$extensions }
9233
9113
  };
9234
9114
  }
9235
- isBasePermutation(modifierInputs, defaults) {
9236
- return Object.entries(modifierInputs).every(([key, value]) => value === defaults[key]);
9237
- }
9238
9115
  };
9239
9116
  function jsonRenderer() {
9240
9117
  const rendererInstance = new JsonRenderer();
@@ -9247,7 +9124,6 @@ function jsonRenderer() {
9247
9124
  }
9248
9125
 
9249
9126
  // src/renderers/tailwind.ts
9250
- init_errors();
9251
9127
  init_token_utils();
9252
9128
 
9253
9129
  // src/renderers/bundlers/tailwind.ts
@@ -9276,6 +9152,13 @@ async function bundleAsTailwind(bundleData, options, formatThemeTokens, formatOv
9276
9152
  }
9277
9153
  return cssBlocks.join("\n");
9278
9154
  }
9155
+ function resolveModifierSelectorAndMedia(options, modifier, context, modifierInputs) {
9156
+ const normalized = normalizeModifierInputs(modifierInputs);
9157
+ return {
9158
+ selector: resolveSelector(options.selector, modifier, context, false, normalized),
9159
+ mediaQuery: resolveMediaQuery(options.mediaQuery, modifier, context, false, normalized)
9160
+ };
9161
+ }
9279
9162
  async function formatModifierOverride({ tokens, modifierInputs }, baseItem, options, formatOverrideBlock) {
9280
9163
  const differenceCount = countModifierDifferences(modifierInputs, baseItem.modifierInputs);
9281
9164
  if (differenceCount > 1) {
@@ -9288,19 +9171,11 @@ async function formatModifierOverride({ tokens, modifierInputs }, baseItem, opti
9288
9171
  const expectedSource = getExpectedSource(modifierInputs, baseItem.modifierInputs);
9289
9172
  const [modifier, context] = parseModifierSource(expectedSource);
9290
9173
  const cleanTokens = stripInternalMetadata(tokensToInclude);
9291
- const selector = resolveSelector(
9292
- options.selector,
9174
+ const { selector, mediaQuery } = resolveModifierSelectorAndMedia(
9175
+ options,
9293
9176
  modifier,
9294
9177
  context,
9295
- false,
9296
- normalizeModifierInputs(modifierInputs)
9297
- );
9298
- const mediaQuery = resolveMediaQuery(
9299
- options.mediaQuery,
9300
- modifier,
9301
- context,
9302
- false,
9303
- normalizeModifierInputs(modifierInputs)
9178
+ modifierInputs
9304
9179
  );
9305
9180
  const css2 = await formatOverrideBlock(cleanTokens, selector, mediaQuery, options.minify);
9306
9181
  return `/* Modifier: ${modifier}=${context} */
@@ -9389,7 +9264,7 @@ var TailwindRenderer = class {
9389
9264
  */
9390
9265
  async formatTokens(tokens, options) {
9391
9266
  const lines = [];
9392
- const indent2 = options.minify ? "" : " ";
9267
+ const indent = options.minify ? "" : " ";
9393
9268
  const newline = options.minify ? "" : "\n";
9394
9269
  const space = options.minify ? "" : " ";
9395
9270
  if (options.includeImport) {
@@ -9411,7 +9286,7 @@ var TailwindRenderer = class {
9411
9286
  for (const [, token] of getSortedTokenEntries(tokens)) {
9412
9287
  const varName = this.buildVariableName(token);
9413
9288
  const varValue = this.formatValue(token);
9414
- lines.push(`${indent2}--${varName}:${space}${varValue};${newline}`);
9289
+ lines.push(`${indent}--${varName}:${space}${varValue};${newline}`);
9415
9290
  }
9416
9291
  lines.push(`}${newline}`);
9417
9292
  const cssString = lines.join("");
@@ -9422,15 +9297,15 @@ var TailwindRenderer = class {
9422
9297
  * Used for modifier overrides (e.g., dark mode) appended after the @theme block.
9423
9298
  */
9424
9299
  async formatOverrideBlock(tokens, selector, mediaQuery, minify) {
9425
- const indent2 = minify ? "" : " ";
9300
+ const indent = minify ? "" : " ";
9426
9301
  const newline = minify ? "" : "\n";
9427
9302
  const space = minify ? "" : " ";
9428
9303
  const hasMediaQuery = mediaQuery !== "";
9429
- const tokenIndent = hasMediaQuery ? indent2 + indent2 : indent2;
9304
+ const tokenIndent = hasMediaQuery ? indent + indent : indent;
9430
9305
  const lines = [];
9431
9306
  if (hasMediaQuery) {
9432
9307
  lines.push(`@media ${mediaQuery}${space}{${newline}`);
9433
- lines.push(`${indent2}${selector}${space}{${newline}`);
9308
+ lines.push(`${indent}${selector}${space}{${newline}`);
9434
9309
  } else {
9435
9310
  lines.push(`${selector}${space}{${newline}`);
9436
9311
  }
@@ -9440,7 +9315,7 @@ var TailwindRenderer = class {
9440
9315
  lines.push(`${tokenIndent}--${varName}:${space}${varValue};${newline}`);
9441
9316
  }
9442
9317
  if (hasMediaQuery) {
9443
- lines.push(`${indent2}}${newline}`);
9318
+ lines.push(`${indent}}${newline}`);
9444
9319
  lines.push(`}${newline}`);
9445
9320
  } else {
9446
9321
  lines.push(`}${newline}`);
@@ -9467,8 +9342,8 @@ var TailwindRenderer = class {
9467
9342
  if (token.$type === "dimension" && isDimensionObject(value)) {
9468
9343
  return dimensionObjectToString(value);
9469
9344
  }
9470
- if (token.$type === "duration" && this.isDurationObject(value)) {
9471
- return `${value.value}${value.unit}`;
9345
+ if (token.$type === "duration" && isDurationObject(value)) {
9346
+ return durationObjectToString(value);
9472
9347
  }
9473
9348
  if (token.$type === "fontFamily") {
9474
9349
  if (Array.isArray(value)) {
@@ -9523,9 +9398,6 @@ var TailwindRenderer = class {
9523
9398
  }
9524
9399
  return parts.join(" ");
9525
9400
  }
9526
- isDurationObject(value) {
9527
- return typeof value === "object" && value !== null && "value" in value && "unit" in value && value.unit !== void 0;
9528
- }
9529
9401
  async formatWithPrettier(css2) {
9530
9402
  try {
9531
9403
  return await prettier.format(css2, {
@@ -9542,7 +9414,7 @@ var TailwindRenderer = class {
9542
9414
  const bundleData = context.permutations.map(({ tokens, modifierInputs }) => ({
9543
9415
  tokens,
9544
9416
  modifierInputs,
9545
- isBase: this.isBasePermutation(modifierInputs, context.meta.defaults)
9417
+ isBase: isBasePermutation(modifierInputs, context.meta.defaults)
9546
9418
  }));
9547
9419
  return await bundleAsTailwind(
9548
9420
  bundleData,
@@ -9552,12 +9424,12 @@ var TailwindRenderer = class {
9552
9424
  );
9553
9425
  }
9554
9426
  async formatStandalone(context, options) {
9555
- const requiresFile = context.buildPath !== void 0 && context.buildPath !== "";
9556
- if (!context.output.file && requiresFile) {
9557
- throw new ConfigurationError(
9558
- `Output "${context.output.name}": file is required for standalone Tailwind output`
9559
- );
9560
- }
9427
+ assertFileRequired(
9428
+ context.buildPath,
9429
+ context.output.file,
9430
+ context.output.name,
9431
+ "standalone Tailwind"
9432
+ );
9561
9433
  const files = {};
9562
9434
  for (const { tokens, modifierInputs } of context.permutations) {
9563
9435
  const processedTokens = stripInternalMetadata(tokens);
@@ -9573,11 +9445,6 @@ var TailwindRenderer = class {
9573
9445
  }
9574
9446
  return outputTree(files);
9575
9447
  }
9576
- isBasePermutation(modifierInputs, defaults) {
9577
- return Object.entries(defaults).every(
9578
- ([key, value]) => modifierInputs[key]?.toLowerCase() === value.toLowerCase()
9579
- );
9580
- }
9581
9448
  };
9582
9449
  function tailwindRenderer() {
9583
9450
  const rendererInstance = new TailwindRenderer();
@@ -9605,7 +9472,7 @@ function css(config) {
9605
9472
  file,
9606
9473
  renderer: cssRenderer(),
9607
9474
  options: { preset, ...rendererOptions },
9608
- transforms,
9475
+ transforms: [nameKebabCase(), ...transforms ?? []],
9609
9476
  filters,
9610
9477
  hooks
9611
9478
  };
@@ -9710,11 +9577,6 @@ function defineRenderer(renderer) {
9710
9577
 
9711
9578
  // src/index.ts
9712
9579
  init_errors();
9713
- /**
9714
- * @license
9715
- * Copyright (c) 2025 Dispersa Contributors
9716
- * SPDX-License-Identifier: MIT
9717
- */
9718
9580
  /**
9719
9581
  * @license MIT
9720
9582
  * Copyright (c) 2025-present Dispersa Contributors
@@ -9722,6 +9584,11 @@ init_errors();
9722
9584
  * This source code is licensed under the MIT license found in the
9723
9585
  * LICENSE file in the root directory of this source tree.
9724
9586
  */
9587
+ /**
9588
+ * @license
9589
+ * Copyright (c) 2025 Dispersa Contributors
9590
+ * SPDX-License-Identifier: MIT
9591
+ */
9725
9592
 
9726
9593
  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 };
9727
9594
  //# sourceMappingURL=index.js.map