@ethlete/core 5.0.0-next.0 → 5.0.0-next.10

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.
@@ -5,10 +5,15 @@
5
5
  "schema": "./migrate-to-v5/schema.json",
6
6
  "description": "Migrate to v5"
7
7
  },
8
- "tailwind-4-theme": {
9
- "factory": "./tailwind-4-theme/generator",
10
- "schema": "./tailwind-4-theme/schema.json",
11
- "description": "Generate theming for Tailwind CSS v4"
8
+ "tailwind-4-color-theme": {
9
+ "factory": "./tailwind-4-color-theme/generator",
10
+ "schema": "./tailwind-4-color-theme/schema.json",
11
+ "description": "Generate color theming for Tailwind CSS v4"
12
+ },
13
+ "tailwind-4-surface-theme": {
14
+ "factory": "./tailwind-4-surface-theme/generator",
15
+ "schema": "./tailwind-4-surface-theme/schema.json",
16
+ "description": "Generate surface theming for Tailwind CSS v4"
12
17
  }
13
18
  }
14
19
  }
@@ -0,0 +1,173 @@
1
+ import { logger } from '@nx/devkit';
2
+ /**
3
+ * Symbol renames in TypeScript files (imports + usage).
4
+ */
5
+ const TS_SYMBOL_RENAMES = {
6
+ ProvideThemeDirective: 'ProvideColorDirective',
7
+ THEME_PROVIDER: 'COLOR_PROVIDER',
8
+ ColorThemedDirective: 'ColoredDirective',
9
+ ColorThemedStylesComponent: 'ColoredStylesComponent',
10
+ SurfaceThemedDirective: 'SurfacedDirective',
11
+ SurfaceThemedStylesComponent: 'SurfacedStylesComponent',
12
+ injectThemesPrefix: 'injectColorThemesPrefix',
13
+ createCssThemeName: 'createCssColorThemeName',
14
+ };
15
+ /**
16
+ * String replacements applied to HTML templates and inline templates.
17
+ */
18
+ const TEMPLATE_REPLACEMENTS = [
19
+ ['[etProvideTheme]', '[etProvideColor]'],
20
+ ['etProvideTheme', 'etProvideColor'],
21
+ ['[etProvideAltTheme]', '[etProvideAltColor]'],
22
+ ['etProvideAltTheme', 'etProvideAltColor'],
23
+ ['[etColorThemed]', '[etColored]'],
24
+ ['etColorThemed', 'etColored'],
25
+ ['[etSurfaceThemed]', '[etSurfaced]'],
26
+ ['etSurfaceThemed', 'etSurfaced'],
27
+ ['[theme]=', '[color]='],
28
+ ['[altTheme]=', '[altColor]='],
29
+ ];
30
+ /**
31
+ * CSS class name replacements.
32
+ */
33
+ const CSS_REPLACEMENTS = [
34
+ ['.et-color-themed', '.et-colored'],
35
+ ['.et-surface-themed', '.et-surfaced'],
36
+ ['.et-theme--', '.et-color--'],
37
+ ['.et-theme-alt--', '.et-color-alt--'],
38
+ ];
39
+ /**
40
+ * Host directive input alias replacements in TypeScript files.
41
+ */
42
+ const HOST_DIRECTIVE_REPLACEMENTS = [
43
+ ["'etProvideTheme:theme'", "'etProvideColor:color'"],
44
+ ["'etProvideAltTheme:altTheme'", "'etProvideAltColor:altColor'"],
45
+ ["'etProvideTheme:color'", "'etProvideColor:color'"],
46
+ ["'etProvideAltTheme:altColor'", "'etProvideAltColor:altColor'"],
47
+ ['"etProvideTheme:theme"', '"etProvideColor:color"'],
48
+ ['"etProvideAltTheme:altTheme"', '"etProvideAltColor:altColor"'],
49
+ ['"etProvideTheme"', '"etProvideColor"'],
50
+ ['"etProvideAltTheme"', '"etProvideAltColor"'],
51
+ ];
52
+ export default async function migrateColorNaming(tree) {
53
+ logger.log('\n🔄 Migrating theme → color naming...\n');
54
+ const tsFiles = [];
55
+ const htmlFiles = [];
56
+ const cssFiles = [];
57
+ findFiles('.', tree, tsFiles, htmlFiles, cssFiles);
58
+ let filesModified = 0;
59
+ for (const filePath of tsFiles) {
60
+ const wasModified = migrateTypeScriptFile(tree, filePath);
61
+ if (wasModified) {
62
+ filesModified++;
63
+ logger.log(` ✓ ${filePath}`);
64
+ }
65
+ }
66
+ for (const filePath of htmlFiles) {
67
+ const wasModified = migrateTemplateFile(tree, filePath);
68
+ if (wasModified) {
69
+ filesModified++;
70
+ logger.log(` ✓ ${filePath}`);
71
+ }
72
+ }
73
+ for (const filePath of cssFiles) {
74
+ const wasModified = migrateCssFile(tree, filePath);
75
+ if (wasModified) {
76
+ filesModified++;
77
+ logger.log(` ✓ ${filePath}`);
78
+ }
79
+ }
80
+ if (filesModified > 0) {
81
+ logger.log(`\n✅ Successfully migrated color naming in ${filesModified} file(s)\n`);
82
+ }
83
+ else {
84
+ logger.log('\nℹ️ No files needed migration\n');
85
+ }
86
+ }
87
+ function findFiles(dir, tree, tsFiles, htmlFiles, cssFiles) {
88
+ const children = tree.children(dir);
89
+ for (const child of children) {
90
+ const path = dir === '.' ? child : `${dir}/${child}`;
91
+ if (tree.isFile(path)) {
92
+ if (path.includes('node_modules'))
93
+ continue;
94
+ if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {
95
+ tsFiles.push(path);
96
+ }
97
+ else if (path.endsWith('.html')) {
98
+ htmlFiles.push(path);
99
+ }
100
+ else if (path.endsWith('.css') || path.endsWith('.scss')) {
101
+ cssFiles.push(path);
102
+ }
103
+ }
104
+ else {
105
+ if (child === 'node_modules')
106
+ continue;
107
+ findFiles(path, tree, tsFiles, htmlFiles, cssFiles);
108
+ }
109
+ }
110
+ }
111
+ function migrateTypeScriptFile(tree, filePath) {
112
+ let content = tree.read(filePath, 'utf-8');
113
+ if (!content)
114
+ return false;
115
+ const original = content;
116
+ // Rename symbols
117
+ for (const [oldName, newName] of Object.entries(TS_SYMBOL_RENAMES)) {
118
+ content = replaceWholeWord(content, oldName, newName);
119
+ }
120
+ // Rename host directive input aliases
121
+ for (const [oldStr, newStr] of HOST_DIRECTIVE_REPLACEMENTS) {
122
+ content = content.replaceAll(oldStr, newStr);
123
+ }
124
+ // Handle inline templates in TypeScript files
125
+ for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
126
+ content = content.replaceAll(oldStr, newStr);
127
+ }
128
+ // Handle CSS in TypeScript files
129
+ for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
130
+ content = content.replaceAll(oldStr, newStr);
131
+ }
132
+ if (content !== original) {
133
+ tree.write(filePath, content);
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+ function migrateTemplateFile(tree, filePath) {
139
+ let content = tree.read(filePath, 'utf-8');
140
+ if (!content)
141
+ return false;
142
+ const original = content;
143
+ for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
144
+ content = content.replaceAll(oldStr, newStr);
145
+ }
146
+ if (content !== original) {
147
+ tree.write(filePath, content);
148
+ return true;
149
+ }
150
+ return false;
151
+ }
152
+ function migrateCssFile(tree, filePath) {
153
+ let content = tree.read(filePath, 'utf-8');
154
+ if (!content)
155
+ return false;
156
+ const original = content;
157
+ for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
158
+ content = content.replaceAll(oldStr, newStr);
159
+ }
160
+ if (content !== original) {
161
+ tree.write(filePath, content);
162
+ return true;
163
+ }
164
+ return false;
165
+ }
166
+ function replaceWholeWord(content, oldWord, newWord) {
167
+ const regex = new RegExp(`\\b${escapeRegex(oldWord)}\\b`, 'g');
168
+ return content.replace(regex, newWord);
169
+ }
170
+ function escapeRegex(str) {
171
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
172
+ }
173
+ //# sourceMappingURL=color-naming.js.map
@@ -1,4 +1,5 @@
1
1
  import { formatFiles } from '@nx/devkit';
2
+ import migrateColorNaming from './color-naming.js';
2
3
  import migrateCreateProvider from './create-provider.js';
3
4
  import migrateRouterStateService from './router-state-service.js';
4
5
  import migrateViewportService from './viewport-service.js';
@@ -18,6 +19,11 @@ export default async function migrate(tree, schema) {
18
19
  if (shouldMigrateRouterStateService) {
19
20
  await migrateRouterStateService(tree);
20
21
  }
22
+ const shouldMigrateColorNaming = schema.migrateColorNaming !== false;
23
+ if (shouldMigrateColorNaming) {
24
+ console.log(' • Migrating theme → color naming...');
25
+ await migrateColorNaming(tree);
26
+ }
21
27
  if (!schema.skipFormat) {
22
28
  console.log(' • Formatting files...');
23
29
  await formatFiles(tree);
@@ -23,6 +23,11 @@
23
23
  "type": "boolean",
24
24
  "description": "Migrate router state service to v5",
25
25
  "default": true
26
+ },
27
+ "migrateColorNaming": {
28
+ "type": "boolean",
29
+ "description": "Migrate theme naming to color naming (ProvideThemeDirective -> ProvideColorDirective, etc.)",
30
+ "default": true
26
31
  }
27
32
  },
28
33
  "required": []
@@ -9,7 +9,7 @@ export default async function generate(tree, schema) {
9
9
  if (!tree.exists(themesPath)) {
10
10
  logger.error(`❌ Themes file not found at: ${themesPath}`);
11
11
  logger.log(`\nPlease specify the correct path using --themesPath option.`);
12
- logger.log(`Example: nx g @ethlete/core:tailwind-4-theme --themesPath=src/app/themes.ts\n`);
12
+ logger.log(`Example: nx g @ethlete/core:tailwind-4-color-theme --themesPath=src/app/themes.ts\n`);
13
13
  return;
14
14
  }
15
15
  logger.log(`📁 Reading themes from: ${themesPath}`);
@@ -167,9 +167,9 @@ function parseThemeObject(obj, sourceFile) {
167
167
  theme.isDefault = true;
168
168
  }
169
169
  break;
170
- case 'isDefaultAlt':
171
- if (initializer.isKind(SyntaxKind.TrueKeyword)) {
172
- theme.isDefaultAlt = true;
170
+ case 'type':
171
+ if (initializer.isKind(SyntaxKind.StringLiteral)) {
172
+ theme.type = initializer.getLiteralValue();
173
173
  }
174
174
  break;
175
175
  case 'primary':
@@ -188,8 +188,6 @@ function parseThemeObject(obj, sourceFile) {
188
188
  }
189
189
  function validateThemeConfiguration(themes) {
190
190
  const defaultThemes = themes.filter((t) => t.isDefault);
191
- const defaultAltThemes = themes.filter((t) => t.isDefaultAlt);
192
- const bothDefaultAndAlt = themes.filter((t) => t.isDefault && t.isDefaultAlt);
193
191
  // Error: No default theme
194
192
  if (defaultThemes.length === 0) {
195
193
  throw new Error('No default theme found. At least one theme must have isDefault: true');
@@ -198,13 +196,18 @@ function validateThemeConfiguration(themes) {
198
196
  if (defaultThemes.length > 1) {
199
197
  throw new Error(`Multiple default themes found: ${defaultThemes.map((t) => t.name).join(', ')}. Only one theme can have isDefault: true`);
200
198
  }
201
- // Error: Theme has both isDefault and isDefaultAlt
202
- if (bothDefaultAndAlt.length > 0) {
203
- throw new Error(`Theme "${bothDefaultAndAlt[0].name}" has both isDefault and isDefaultAlt set to true. A theme can only be one or the other`);
204
- }
205
- // Error: Multiple default alt themes
206
- if (defaultAltThemes.length > 1) {
207
- throw new Error(`Multiple default alt themes found: ${defaultAltThemes.map((t) => t.name).join(', ')}. Only one theme can have isDefaultAlt: true`);
199
+ // Error: Duplicate theme types
200
+ const typedThemes = themes.filter((t) => t.type);
201
+ const typeMap = new Map();
202
+ for (const theme of typedThemes) {
203
+ const existing = typeMap.get(theme.type) || [];
204
+ existing.push(theme.name);
205
+ typeMap.set(theme.type, existing);
206
+ }
207
+ for (const [type, names] of typeMap) {
208
+ if (names.length > 1) {
209
+ throw new Error(`Multiple themes with type "${type}" found: ${names.join(', ')}. Only one theme can have a given type`);
210
+ }
208
211
  }
209
212
  }
210
213
  function parseThemeSwatch(obj, sourceFile) {
@@ -219,7 +222,7 @@ function parseThemeSwatch(obj, sourceFile) {
219
222
  if (!initializer) {
220
223
  continue;
221
224
  }
222
- if (propName === 'color' || propName === 'onColor') {
225
+ if (propName === 'color' || propName === 'onColor' || propName === 'inkColor') {
223
226
  const colorMap = parseColorMap(initializer, sourceFile);
224
227
  if (colorMap) {
225
228
  swatch[propName] = colorMap;
@@ -306,14 +309,13 @@ function generateTailwindThemeCss(themes, prefix, schema) {
306
309
  *
307
310
  * Generated from your theme definitions
308
311
  * This file can be regenerated by running:
309
- * nx g @ethlete/core:tailwind-4-theme --themesPath=${themesPath}${schema.outputPath ? ` --outputPath=${outputPath}` : ''}${schema.prefix && schema.prefix !== 'et' ? ` --prefix=${schema.prefix}` : ''}
312
+ * nx g @ethlete/core:tailwind-4-color-theme --themesPath=${themesPath}${schema.outputPath ? ` --outputPath=${outputPath}` : ''}${schema.prefix && schema.prefix !== 'et' ? ` --prefix=${schema.prefix}` : ''}
310
313
  */
311
314
 
312
315
  `;
313
316
  // Validation is now done separately before this function is called
314
317
  const defaultThemes = themes.filter((t) => t.isDefault);
315
- const defaultAltThemes = themes.filter((t) => t.isDefaultAlt);
316
- const regularThemes = themes.filter((t) => !t.isDefault && !t.isDefaultAlt);
318
+ const regularThemes = themes.filter((t) => !t.isDefault);
317
319
  // Generate static Tailwind @theme block for each theme
318
320
  for (const theme of themes) {
319
321
  const name = createCssThemeName(theme.name);
@@ -325,6 +327,9 @@ function generateTailwindThemeCss(themes, prefix, schema) {
325
327
  // On colors for Tailwind utilities
326
328
  addTailwindColorVariants(tailwindVars, `${prefix}-on-${name}`, theme.primary.onColor);
327
329
  tailwindVars.push('');
330
+ // Ink colors for standalone foreground usage
331
+ addTailwindColorVariants(tailwindVars, `${prefix}-${name}-ink`, theme.primary.inkColor || theme.primary.color);
332
+ tailwindVars.push('');
328
333
  // Secondary colors if present
329
334
  if (theme.secondary) {
330
335
  addTailwindColorVariants(tailwindVars, `${prefix}-${name}-secondary`, theme.secondary.color);
@@ -341,68 +346,67 @@ function generateTailwindThemeCss(themes, prefix, schema) {
341
346
  }
342
347
  }
343
348
  tailwindVars.push('');
344
- // Generate dynamic theme variables - check main themes and alt themes separately
349
+ // Main theme dynamic colors
345
350
  const mainThemes = [...defaultThemes, ...regularThemes];
346
351
  const hasSecondary = mainThemes.some((t) => t.secondary);
347
352
  const hasTertiary = mainThemes.some((t) => t.tertiary);
348
- const hasAltSecondary = defaultAltThemes.some((t) => t.secondary);
349
- const hasAltTertiary = defaultAltThemes.some((t) => t.tertiary);
350
- // Main theme dynamic colors
351
353
  tailwindVars.push(' /* Dynamic theme colors (references runtime CSS variables) */');
352
354
  addDynamicThemeColors(tailwindVars, prefix, 'theme', 'primary', true);
355
+ addDynamicInkColors(tailwindVars, prefix, 'theme-ink', 'primary-ink');
353
356
  if (hasSecondary) {
354
357
  addDynamicThemeColors(tailwindVars, prefix, 'theme-secondary', 'secondary', false);
355
358
  }
356
359
  if (hasTertiary) {
357
360
  addDynamicThemeColors(tailwindVars, prefix, 'theme-tertiary', 'tertiary', false);
358
361
  }
359
- // Alt theme dynamic colors
360
- tailwindVars.push(' /* Alt theme dynamic colors */');
361
- addDynamicThemeColors(tailwindVars, prefix, 'alt-theme', 'alt-primary', true);
362
- if (hasAltSecondary) {
363
- addDynamicThemeColors(tailwindVars, prefix, 'alt-theme-secondary', 'alt-secondary', false);
364
- }
365
- if (hasAltTertiary) {
366
- addDynamicThemeColors(tailwindVars, prefix, 'alt-theme-tertiary', 'alt-tertiary', false);
367
- }
368
- // Generate runtime CSS for ALL themes (both main and alt variants)
362
+ // Generate runtime CSS for all themes
369
363
  themes.forEach((theme) => {
370
364
  const name = createCssThemeName(theme.name);
371
- // Determine if this is the default or default-alt theme
365
+ // Determine if this is the default theme
372
366
  const isDefault = theme.isDefault;
373
- const isDefaultAlt = theme.isDefaultAlt;
374
- // Generate main theme variant (.et-theme--{name})
367
+ // Generate color variant (.et-color--{name})
375
368
  if (isDefault) {
376
- // Default theme gets :root and .et-theme--default selectors
377
- const selectors = [':root', `.${prefix}-theme--default`, `.${prefix}-theme--${name}`];
378
- themeVars.push(`${selectors.join(', ')} {`);
379
- }
380
- else if (!isDefaultAlt) {
381
- // Regular themes just get their own class
382
- themeVars.push(`.${prefix}-theme--${name} {`);
383
- }
384
- if (isDefault || !isDefaultAlt) {
385
- addThemeColorVariants(themeVars, prefix, '', theme);
386
- themeVars.push('}\n');
387
- }
388
- // Generate alt theme variant (.et-theme-alt--{name})
389
- if (isDefaultAlt) {
390
- // Default alt theme gets :root and .et-theme-alt--default selectors
391
- const selectors = [':root', `.${prefix}-theme-alt--default`, `.${prefix}-theme-alt--${name}`];
369
+ // Default color gets :root and .et-color--default selectors
370
+ const selectors = [':root', `.${prefix}-color--default`, `.${prefix}-color--${name}`];
392
371
  themeVars.push(`${selectors.join(', ')} {`);
393
372
  }
394
373
  else {
395
- // All themes (including default and regular) get an alt variant
396
- themeVars.push(`.${prefix}-theme-alt--${name} {`);
374
+ // Regular colors just get their own class
375
+ themeVars.push(`.${prefix}-color--${name} {`);
397
376
  }
398
- addThemeColorVariants(themeVars, prefix, 'alt-', theme);
377
+ addThemeColorVariants(themeVars, prefix, '', theme);
399
378
  themeVars.push('}\n');
400
379
  });
380
+ // Convenience var aliases — available on any element with a color class (or root for default)
381
+ const aliasBlock = `/* Convenience aliases (rgb + solid + opacity variants) */
382
+ @layer base {
383
+ :root, :where([class*="${prefix}-color--"]) {
384
+ --${prefix}-theme-color-primary-rgb: var(--${prefix}-color-primary);
385
+ --${prefix}-theme-color-primary-opacity: 1;
386
+ --${prefix}-theme-color-primary-solid: rgb(var(--${prefix}-theme-color-primary-rgb));
387
+ --${prefix}-theme-color-primary: rgb(var(--${prefix}-theme-color-primary-rgb) / var(--${prefix}-theme-color-primary-opacity));
388
+
389
+ --${prefix}-theme-color-on-primary-rgb: var(--${prefix}-color-on-primary);
390
+ --${prefix}-theme-color-on-primary-opacity: 1;
391
+ --${prefix}-theme-color-on-primary-solid: rgb(var(--${prefix}-theme-color-on-primary-rgb) / var(--${prefix}-theme-color-on-primary-opacity));
392
+ --${prefix}-theme-color-on-primary: rgb(var(--${prefix}-theme-color-on-primary-rgb) / var(--${prefix}-theme-color-on-primary-opacity));
393
+
394
+ --${prefix}-theme-color-ink-rgb: var(--${prefix}-color-primary-ink, var(--${prefix}-color-primary));
395
+ --${prefix}-theme-color-ink-opacity: 1;
396
+ --${prefix}-theme-color-ink-solid: rgb(var(--${prefix}-theme-color-ink-rgb) / var(--${prefix}-theme-color-ink-opacity));
397
+ --${prefix}-theme-color-ink: rgb(var(--${prefix}-theme-color-ink-rgb) / var(--${prefix}-theme-color-ink-opacity));
398
+ }
399
+ }
400
+ `;
401
401
  return `${header}@theme {
402
402
  ${tailwindVars.join('\n')}
403
403
  }
404
404
 
405
- ${themeVars.join('\n')}`;
405
+ @layer base {
406
+ ${themeVars.join('\n')}
407
+ }
408
+
409
+ ${aliasBlock}`;
406
410
  }
407
411
  function addDynamicThemeColors(vars, prefix, tailwindName, cssVarName, addSpacingBefore) {
408
412
  if (addSpacingBefore && vars.length > 0 && vars[vars.length - 1] !== '') {
@@ -423,6 +427,14 @@ function addDynamicThemeColors(vars, prefix, tailwindName, cssVarName, addSpacin
423
427
  vars.push(` --color-${prefix}-on-${tailwindName}-disabled: rgb(var(--${prefix}-color-on-${cssVarName}-disabled));`);
424
428
  vars.push('');
425
429
  }
430
+ function addDynamicInkColors(vars, prefix, tailwindName, cssVarName) {
431
+ vars.push(` --color-${prefix}-${tailwindName}: rgb(var(--${prefix}-color-${cssVarName}));`);
432
+ vars.push(` --color-${prefix}-${tailwindName}-hover: rgb(var(--${prefix}-color-${cssVarName}-hover));`);
433
+ vars.push(` --color-${prefix}-${tailwindName}-focus: rgb(var(--${prefix}-color-${cssVarName}-focus));`);
434
+ vars.push(` --color-${prefix}-${tailwindName}-active: rgb(var(--${prefix}-color-${cssVarName}-active));`);
435
+ vars.push(` --color-${prefix}-${tailwindName}-disabled: rgb(var(--${prefix}-color-${cssVarName}-disabled));`);
436
+ vars.push('');
437
+ }
426
438
  function addTailwindColorVariants(vars, colorName, colorSet) {
427
439
  // Tailwind 4 requires --color-* prefix and rgb() wrapper
428
440
  // Always generate all variants with fallbacks
@@ -465,6 +477,17 @@ function addThemeColorVariants(vars, prefix, altPrefix, theme) {
465
477
  vars.push(` --${prefix}-color-${altPrefix}on-${level}-focus: ${onFocusColor};`);
466
478
  vars.push(` --${prefix}-color-${altPrefix}on-${level}-active: ${onActiveColor};`);
467
479
  vars.push(` --${prefix}-color-${altPrefix}on-${level}-disabled: ${onDisabledColor};`);
480
+ const inkDefaultColor = swatch.inkColor?.default || defaultColor;
481
+ const inkHoverColor = swatch.inkColor?.hover || inkDefaultColor;
482
+ const inkFocusColor = swatch.inkColor?.focus || inkHoverColor;
483
+ const inkActiveColor = swatch.inkColor?.active || inkDefaultColor;
484
+ const inkDisabledColor = swatch.inkColor?.disabled || inkDefaultColor;
485
+ vars.push('');
486
+ vars.push(` --${prefix}-color-${altPrefix}${level}-ink: ${inkDefaultColor};`);
487
+ vars.push(` --${prefix}-color-${altPrefix}${level}-ink-hover: ${inkHoverColor};`);
488
+ vars.push(` --${prefix}-color-${altPrefix}${level}-ink-focus: ${inkFocusColor};`);
489
+ vars.push(` --${prefix}-color-${altPrefix}${level}-ink-active: ${inkActiveColor};`);
490
+ vars.push(` --${prefix}-color-${altPrefix}${level}-ink-disabled: ${inkDisabledColor};`);
468
491
  if (theme.secondary || theme.tertiary) {
469
492
  vars.push('');
470
493
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/schema",
3
- "$id": "tailwind-4-theme",
3
+ "$id": "tailwind-4-color-theme",
4
4
  "title": "Tailwind 4 Theming Generator",
5
5
  "description": "Generates Tailwind 4 compatible CSS from your Ethlete theme definitions",
6
6
  "type": "object",