igniteui-theming 25.1.0 → 25.2.0

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.
Files changed (128) hide show
  1. package/dist/index.d.ts +75 -0
  2. package/dist/index.js +12 -0
  3. package/dist/json/components/bootstrap.json +1 -0
  4. package/dist/json/components/fluent.json +1 -0
  5. package/dist/json/components/indigo.json +1 -0
  6. package/dist/json/components/material.json +1 -0
  7. package/{json → dist/json}/components/themes.json +31 -1
  8. package/dist/mcp/generators/css.d.ts +7 -4
  9. package/dist/mcp/generators/css.js +129 -104
  10. package/dist/mcp/generators/sass.js +227 -254
  11. package/dist/mcp/index.js +259 -323
  12. package/dist/mcp/knowledge/color-usage.js +524 -502
  13. package/dist/mcp/knowledge/colors.js +61 -50
  14. package/dist/mcp/knowledge/component-metadata.js +697 -598
  15. package/dist/mcp/knowledge/component-themes.js +70 -57
  16. package/dist/mcp/knowledge/custom-palettes.js +4 -9
  17. package/dist/mcp/knowledge/docs/colors/guidance.js +4 -0
  18. package/dist/mcp/knowledge/docs/colors/usage.js +4 -0
  19. package/dist/mcp/knowledge/docs/layout/functions/border-radius.js +4 -0
  20. package/dist/mcp/knowledge/docs/layout/functions/pad.js +4 -0
  21. package/dist/mcp/knowledge/docs/layout/functions/sizable.js +4 -0
  22. package/dist/mcp/knowledge/docs/layout/mixins/sizable.js +4 -0
  23. package/dist/mcp/knowledge/docs/layout/mixins/sizing.js +4 -0
  24. package/dist/mcp/knowledge/docs/layout/mixins/spacing.js +4 -0
  25. package/dist/mcp/knowledge/docs/layout/overview.js +4 -0
  26. package/dist/mcp/knowledge/docs/setup/platform.js +4 -0
  27. package/dist/mcp/knowledge/elevations.d.ts +1 -1
  28. package/dist/mcp/knowledge/elevations.js +26 -12
  29. package/dist/mcp/knowledge/index.js +23 -87
  30. package/dist/mcp/knowledge/layout-docs.d.ts +1 -1
  31. package/dist/mcp/knowledge/multipliers.js +5 -0
  32. package/dist/mcp/knowledge/palettes.js +29 -17
  33. package/dist/mcp/knowledge/platforms/angular.js +98 -120
  34. package/dist/mcp/knowledge/platforms/blazor.js +39 -34
  35. package/dist/mcp/knowledge/platforms/common.js +83 -68
  36. package/dist/mcp/knowledge/platforms/index.js +265 -242
  37. package/dist/mcp/knowledge/platforms/react.js +43 -35
  38. package/dist/mcp/knowledge/platforms/webcomponents.js +266 -292
  39. package/dist/mcp/knowledge/sass-api.js +1 -0
  40. package/dist/mcp/knowledge/typography.js +13 -5
  41. package/dist/mcp/resources/index.js +1 -0
  42. package/dist/mcp/resources/presets.js +409 -508
  43. package/dist/mcp/theming/dist/json/colors/meta/multipliers.js +50 -0
  44. package/dist/mcp/theming/dist/json/colors/presets/palettes.js +85 -0
  45. package/dist/mcp/theming/dist/json/components/themes.js +5792 -0
  46. package/dist/mcp/theming/dist/json/elevations/indigo.js +29 -0
  47. package/dist/mcp/theming/dist/json/elevations/material.js +3 -0
  48. package/dist/mcp/theming/dist/json/typography/presets/typescales.js +621 -0
  49. package/dist/mcp/tools/descriptions.js +98 -154
  50. package/dist/mcp/tools/handlers/color.js +58 -56
  51. package/dist/mcp/tools/handlers/component-theme.js +163 -225
  52. package/dist/mcp/tools/handlers/component-tokens.js +159 -219
  53. package/dist/mcp/tools/handlers/custom-palette.js +138 -179
  54. package/dist/mcp/tools/handlers/elevations.js +27 -28
  55. package/dist/mcp/tools/handlers/index.js +11 -0
  56. package/dist/mcp/tools/handlers/layout.js +125 -176
  57. package/dist/mcp/tools/handlers/palette.js +105 -120
  58. package/dist/mcp/tools/handlers/platform.js +289 -311
  59. package/dist/mcp/tools/handlers/resource.js +22 -31
  60. package/dist/mcp/tools/handlers/theme.js +86 -103
  61. package/dist/mcp/tools/handlers/typography.js +29 -30
  62. package/dist/mcp/tools/index.js +13 -0
  63. package/dist/mcp/tools/schemas.js +239 -218
  64. package/dist/mcp/utils/color.js +277 -239
  65. package/dist/mcp/utils/preprocessing.js +57 -30
  66. package/dist/mcp/utils/result.js +43 -45
  67. package/dist/mcp/utils/sass.js +271 -191
  68. package/dist/mcp/utils/theming-resolve.d.ts +19 -0
  69. package/dist/mcp/utils/theming-resolve.js +57 -0
  70. package/dist/mcp/utils/types.js +96 -53
  71. package/dist/mcp/validators/custom-palette.js +218 -243
  72. package/dist/mcp/validators/index.js +3 -0
  73. package/dist/mcp/validators/palette.js +231 -229
  74. package/dist/tailwind/utilities/bootstrap.css +1 -0
  75. package/dist/tailwind/utilities/fluent.css +1 -0
  76. package/dist/tailwind/utilities/indigo.css +1 -0
  77. package/dist/tailwind/utilities/material.css +1 -0
  78. package/package.json +45 -64
  79. package/sass/json/README.md +12 -7
  80. package/sass/themes/_mixins.scss +1 -0
  81. package/sass/themes/components/button-group/_button-group-theme.scss +42 -0
  82. package/sass/themes/components/grid/_grid-theme.scss +1 -1
  83. package/sass/themes/schemas/components/dark/_button-group.scss +173 -50
  84. package/sass/themes/schemas/components/dark/_grid.scss +0 -16
  85. package/sass/themes/schemas/components/light/_button-group.scss +221 -99
  86. package/sass/themes/schemas/components/light/_grid.scss +14 -20
  87. package/LICENSE +0 -21
  88. package/README.md +0 -391
  89. package/dist/mcp/json/colors/presets/palettes.json.js +0 -13
  90. package/dist/mcp/json/components/themes.json.js +0 -143
  91. package/dist/mcp/json/elevations/indigo.json.js +0 -8
  92. package/dist/mcp/json/elevations/material.json.js +0 -8
  93. package/dist/mcp/json/typography/presets/typescales.json.js +0 -17
  94. package/dist/mcp/knowledge/docs/colors/guidance.md.js +0 -4
  95. package/dist/mcp/knowledge/docs/colors/usage.md.js +0 -4
  96. package/dist/mcp/knowledge/docs/layout/functions/border-radius.md.js +0 -4
  97. package/dist/mcp/knowledge/docs/layout/functions/pad.md.js +0 -4
  98. package/dist/mcp/knowledge/docs/layout/functions/sizable.md.js +0 -4
  99. package/dist/mcp/knowledge/docs/layout/mixins/sizable.md.js +0 -4
  100. package/dist/mcp/knowledge/docs/layout/mixins/sizing.md.js +0 -4
  101. package/dist/mcp/knowledge/docs/layout/mixins/spacing.md.js +0 -4
  102. package/dist/mcp/knowledge/docs/layout/overview.md.js +0 -4
  103. package/dist/mcp/knowledge/docs/setup/platform.md.js +0 -4
  104. package/dist/mcp/vite-env.d.ts +0 -18
  105. package/index.js +0 -5
  106. package/json/components/bootstrap.json +0 -1
  107. package/json/components/fluent.json +0 -1
  108. package/json/components/indigo.json +0 -1
  109. package/json/components/material.json +0 -1
  110. package/tailwind/utilities/bootstrap.css +0 -1
  111. package/tailwind/utilities/fluent.css +0 -1
  112. package/tailwind/utilities/indigo.css +0 -1
  113. package/tailwind/utilities/material.css +0 -1
  114. /package/{json → dist/json}/colors/meta/multipliers.json +0 -0
  115. /package/{json → dist/json}/colors/meta/palette.json +0 -0
  116. /package/{json → dist/json}/colors/presets/palettes.json +0 -0
  117. /package/{json → dist/json}/elevations/indigo.json +0 -0
  118. /package/{json → dist/json}/elevations/material.json +0 -0
  119. /package/{json → dist/json}/typography/presets/typescales.json +0 -0
  120. /package/{tailwind → dist/tailwind}/themes/base.css +0 -0
  121. /package/{tailwind → dist/tailwind}/themes/dark/bootstrap.css +0 -0
  122. /package/{tailwind → dist/tailwind}/themes/dark/fluent.css +0 -0
  123. /package/{tailwind → dist/tailwind}/themes/dark/indigo.css +0 -0
  124. /package/{tailwind → dist/tailwind}/themes/dark/material.css +0 -0
  125. /package/{tailwind → dist/tailwind}/themes/light/bootstrap.css +0 -0
  126. /package/{tailwind → dist/tailwind}/themes/light/fluent.css +0 -0
  127. /package/{tailwind → dist/tailwind}/themes/light/indigo.css +0 -0
  128. /package/{tailwind → dist/tailwind}/themes/light/material.css +0 -0
@@ -1,58 +1,101 @@
1
- const PLATFORMS = [
2
- "angular",
3
- "webcomponents",
4
- "react",
5
- "blazor",
6
- "generic"
1
+ //#region src/utils/types.ts
2
+ /**
3
+ * Shared TypeScript types for the MCP server.
4
+ *
5
+ * This module is the SINGLE SOURCE OF TRUTH for shared types and constants.
6
+ * Other modules should import from here rather than redefining types.
7
+ *
8
+ * IMPORTANT: This module should have NO imports from knowledge/ to avoid
9
+ * circular dependencies. Knowledge modules may import from this module.
10
+ */
11
+ /**
12
+ * Supported target platforms for code generation.
13
+ *
14
+ * - angular: Ignite UI for Angular (uses igniteui-angular/theming)
15
+ * - webcomponents: Ignite UI for Web Components (uses igniteui-theming directly)
16
+ * - react: Ignite UI for React (uses igniteui-theming directly)
17
+ * - blazor: Ignite UI for Blazor (uses igniteui-theming for Sass compilation)
18
+ * - generic: Platform-agnostic output using igniteui-theming directly (no Ignite UI product)
19
+ */
20
+ var PLATFORMS = [
21
+ "angular",
22
+ "webcomponents",
23
+ "react",
24
+ "blazor",
25
+ "generic"
7
26
  ];
8
- const DESIGN_SYSTEMS = [
9
- "material",
10
- "bootstrap",
11
- "fluent",
12
- "indigo"
27
+ /**
28
+ * Supported design systems.
29
+ */
30
+ var DESIGN_SYSTEMS = [
31
+ "material",
32
+ "bootstrap",
33
+ "fluent",
34
+ "indigo"
13
35
  ];
14
- const VARIANTS = ["light", "dark"];
15
- const ELEVATION_PRESETS = ["material", "indigo"];
16
- const OUTPUT_FORMATS = ["sass", "css"];
17
- const SHADE_LEVELS = [
18
- "50",
19
- "100",
20
- "200",
21
- "300",
22
- "400",
23
- "500",
24
- "600",
25
- "700",
26
- "800",
27
- "900"
36
+ /**
37
+ * Supported theme variants.
38
+ */
39
+ var VARIANTS = ["light", "dark"];
40
+ /**
41
+ * Supported elevation presets.
42
+ */
43
+ var ELEVATION_PRESETS = ["material", "indigo"];
44
+ /**
45
+ * Supported output formats for code generation.
46
+ *
47
+ * - sass: Generates Sass code using the igniteui-theming library functions
48
+ * - css: Generates CSS custom properties (variables) directly
49
+ */
50
+ var OUTPUT_FORMATS = ["sass", "css"];
51
+ /**
52
+ * Standard shade levels used in the theming system.
53
+ */
54
+ var SHADE_LEVELS = [
55
+ "50",
56
+ "100",
57
+ "200",
58
+ "300",
59
+ "400",
60
+ "500",
61
+ "600",
62
+ "700",
63
+ "800",
64
+ "900"
28
65
  ];
29
- const ACCENT_SHADE_LEVELS = ["A100", "A200", "A400", "A700"];
30
- const ALL_COLOR_SHADES = [
31
- ...SHADE_LEVELS,
32
- ...ACCENT_SHADE_LEVELS
66
+ /**
67
+ * Accent shade levels (Material Design style).
68
+ */
69
+ var ACCENT_SHADE_LEVELS = [
70
+ "A100",
71
+ "A200",
72
+ "A400",
73
+ "A700"
33
74
  ];
34
- const PALETTE_COLOR_GROUPS = [
35
- "primary",
36
- "secondary",
37
- "gray",
38
- "surface",
39
- "info",
40
- "success",
41
- "warn",
42
- "error"
75
+ /**
76
+ * All chromatic shade levels (standard + accent).
77
+ * Derived from SHADE_LEVELS and ACCENT_SHADE_LEVELS to maintain single source of truth.
78
+ */
79
+ var ALL_COLOR_SHADES = [...SHADE_LEVELS, ...ACCENT_SHADE_LEVELS];
80
+ /**
81
+ * All palette color groups.
82
+ * These are the color families that make up a complete palette.
83
+ */
84
+ var PALETTE_COLOR_GROUPS = [
85
+ "primary",
86
+ "secondary",
87
+ "gray",
88
+ "surface",
89
+ "info",
90
+ "success",
91
+ "warn",
92
+ "error"
43
93
  ];
44
- const CHROMATIC_COLOR_GROUPS = PALETTE_COLOR_GROUPS.filter(
45
- (g) => g !== "gray"
46
- );
47
- export {
48
- ACCENT_SHADE_LEVELS,
49
- ALL_COLOR_SHADES,
50
- CHROMATIC_COLOR_GROUPS,
51
- DESIGN_SYSTEMS,
52
- ELEVATION_PRESETS,
53
- OUTPUT_FORMATS,
54
- PALETTE_COLOR_GROUPS,
55
- PLATFORMS,
56
- SHADE_LEVELS,
57
- VARIANTS
58
- };
94
+ /**
95
+ * Chromatic color groups (excludes gray which has different shade handling).
96
+ * These groups use 14 shades (50-900, A100-A700).
97
+ * Derived from PALETTE_COLOR_GROUPS to maintain single source of truth.
98
+ */
99
+ var CHROMATIC_COLOR_GROUPS = PALETTE_COLOR_GROUPS.filter((g) => g !== "gray");
100
+ //#endregion
101
+ export { ACCENT_SHADE_LEVELS, ALL_COLOR_SHADES, CHROMATIC_COLOR_GROUPS, DESIGN_SYSTEMS, ELEVATION_PRESETS, OUTPUT_FORMATS, PALETTE_COLOR_GROUPS, PLATFORMS, SHADE_LEVELS, VARIANTS };
@@ -1,257 +1,232 @@
1
- import "../knowledge/colors.js";
2
- import "../knowledge/component-metadata.js";
3
- import "../knowledge/custom-palettes.js";
4
- import { CHROMATIC_COLOR_GROUPS, ALL_COLOR_SHADES, SHADE_LEVELS } from "../utils/types.js";
5
- import "../knowledge/palettes.js";
6
- import "node:fs";
7
- import "node:path";
8
- import { validateColorsInBatch, DEFAULT_HUE_TOLERANCE, analyzeColorsWithHue, huesAreClose } from "../utils/color.js";
1
+ import { analyzeColorsWithHue, huesAreClose, validateColorsInBatch } from "../utils/color.js";
2
+ import { ALL_COLOR_SHADES, CHROMATIC_COLOR_GROUPS, SHADE_LEVELS } from "../utils/types.js";
3
+ import "../knowledge/index.js";
9
4
  import { formatValidationMessages } from "../utils/result.js";
5
+ //#region src/validators/custom-palette.ts
6
+ /**
7
+ * Validation for custom palette structures.
8
+ *
9
+ * Uses the unified ValidationResult type from result.ts for consistent
10
+ * error/warning handling across the codebase.
11
+ *
12
+ * Performance optimization: Uses batch color validation to minimize Sass
13
+ * compilations. Instead of validating each color individually (which would
14
+ * spawn ~100+ Sass processes), we collect all colors and validate them in
15
+ * a single Sass compilation.
16
+ */
17
+ /**
18
+ * Helper to create a field path from color group and optional shade.
19
+ */
10
20
  function makeFieldPath(colorGroup, shade) {
11
- return shade ? `${colorGroup}.${shade}` : colorGroup;
21
+ return shade ? `${colorGroup}.${shade}` : colorGroup;
12
22
  }
23
+ /**
24
+ * Collects all colors from a palette input for batch validation.
25
+ */
13
26
  function collectAllColors(input) {
14
- const colors = [];
15
- const missingShades = [];
16
- for (const group of CHROMATIC_COLOR_GROUPS) {
17
- const definition = input[group];
18
- if (definition) {
19
- collectFromDefinition(
20
- group,
21
- definition,
22
- ALL_COLOR_SHADES,
23
- colors,
24
- missingShades
25
- );
26
- }
27
- }
28
- if (input.gray) {
29
- collectFromDefinition(
30
- "gray",
31
- input.gray,
32
- [...SHADE_LEVELS],
33
- colors,
34
- missingShades
35
- );
36
- }
37
- return { colors, missingShades };
27
+ const colors = [];
28
+ const missingShades = [];
29
+ for (const group of CHROMATIC_COLOR_GROUPS) {
30
+ const definition = input[group];
31
+ if (definition) collectFromDefinition(group, definition, ALL_COLOR_SHADES, colors, missingShades);
32
+ }
33
+ if (input.gray) collectFromDefinition("gray", input.gray, [...SHADE_LEVELS], colors, missingShades);
34
+ return {
35
+ colors,
36
+ missingShades
37
+ };
38
38
  }
39
+ /**
40
+ * Collects colors from a single color definition.
41
+ */
39
42
  function collectFromDefinition(groupName, definition, expectedShades, colors, missingShades) {
40
- if (definition.mode === "shades") {
41
- colors.push({
42
- key: `${groupName}.baseColor`,
43
- color: definition.baseColor,
44
- groupName
45
- });
46
- } else {
47
- for (const shade of expectedShades) {
48
- const color = definition.shades[shade];
49
- if (!color) {
50
- missingShades.push({
51
- field: makeFieldPath(groupName, shade),
52
- message: `Missing required shade: ${shade}`
53
- });
54
- } else {
55
- colors.push({
56
- key: `${groupName}.${shade}`,
57
- color,
58
- groupName,
59
- shade
60
- });
61
- }
62
- }
63
- if (definition.contrastOverrides) {
64
- for (const [shade, color] of Object.entries(
65
- definition.contrastOverrides
66
- )) {
67
- if (expectedShades.includes(shade)) {
68
- colors.push({
69
- key: `${groupName}.contrast.${shade}`,
70
- color,
71
- groupName,
72
- shade,
73
- isContrast: true
74
- });
75
- }
76
- }
77
- }
78
- }
43
+ if (definition.mode === "shades") colors.push({
44
+ key: `${groupName}.baseColor`,
45
+ color: definition.baseColor,
46
+ groupName
47
+ });
48
+ else {
49
+ for (const shade of expectedShades) {
50
+ const color = definition.shades[shade];
51
+ if (!color) missingShades.push({
52
+ field: makeFieldPath(groupName, shade),
53
+ message: `Missing required shade: ${shade}`
54
+ });
55
+ else colors.push({
56
+ key: `${groupName}.${shade}`,
57
+ color,
58
+ groupName,
59
+ shade
60
+ });
61
+ }
62
+ if (definition.contrastOverrides) {
63
+ for (const [shade, color] of Object.entries(definition.contrastOverrides)) if (expectedShades.includes(shade)) colors.push({
64
+ key: `${groupName}.contrast.${shade}`,
65
+ color,
66
+ groupName,
67
+ shade,
68
+ isContrast: true
69
+ });
70
+ }
71
+ }
79
72
  }
73
+ /**
74
+ * Validates a custom palette input structure.
75
+ *
76
+ * @param input - The custom palette input to validate
77
+ * @param variant - Theme variant for gray shade progression validation (defaults to 'light')
78
+ */
80
79
  async function validateCustomPalette(input, variant = "light") {
81
- const errors = [];
82
- const warnings = [];
83
- const { colors, missingShades } = collectAllColors(input);
84
- errors.push(...missingShades);
85
- const chromaticShadeSet = ALL_COLOR_SHADES;
86
- const grayShadeSet = SHADE_LEVELS;
87
- for (const group of CHROMATIC_COLOR_GROUPS) {
88
- const definition = input[group];
89
- if (definition?.mode === "explicit" && definition.contrastOverrides) {
90
- for (const shade of Object.keys(definition.contrastOverrides)) {
91
- if (!chromaticShadeSet.includes(shade)) {
92
- errors.push({
93
- field: makeFieldPath(group, shade),
94
- message: `Invalid contrast override key: ${shade}. Valid keys are: ${ALL_COLOR_SHADES.join(", ")}`
95
- });
96
- }
97
- }
98
- }
99
- }
100
- if (input.gray?.mode === "explicit" && input.gray.contrastOverrides) {
101
- for (const shade of Object.keys(input.gray.contrastOverrides)) {
102
- if (!grayShadeSet.includes(shade)) {
103
- errors.push({
104
- field: makeFieldPath("gray", shade),
105
- message: `Invalid contrast override key: ${shade}. Valid keys are: ${SHADE_LEVELS.join(", ")}`
106
- });
107
- }
108
- }
109
- }
110
- if (colors.length > 0) {
111
- const colorMap = {};
112
- for (const c of colors) {
113
- colorMap[c.key] = c.color;
114
- }
115
- const validationResults = await validateColorsInBatch(colorMap);
116
- for (const c of colors) {
117
- if (!validationResults[c.key]) {
118
- if (c.isContrast) {
119
- errors.push({
120
- field: makeFieldPath(c.groupName, `contrast.${c.shade}`),
121
- message: `Invalid contrast color for shade ${c.shade}: ${c.color}`,
122
- currentValue: c.color
123
- });
124
- } else if (c.shade) {
125
- errors.push({
126
- field: makeFieldPath(c.groupName, c.shade),
127
- message: `Invalid color value for shade ${c.shade}: ${c.color}`,
128
- currentValue: c.color
129
- });
130
- } else {
131
- errors.push({
132
- field: c.groupName,
133
- message: `Invalid base color: ${c.color}`,
134
- currentValue: c.color
135
- });
136
- }
137
- }
138
- }
139
- }
140
- for (const group of CHROMATIC_COLOR_GROUPS) {
141
- const definition = input[group];
142
- if (definition?.mode === "explicit") {
143
- await validateShadeProgression(
144
- group,
145
- definition.shades,
146
- "chromatic",
147
- variant,
148
- warnings
149
- );
150
- await validateMonochromaticHue(group, definition.shades, warnings);
151
- }
152
- }
153
- if (input.gray?.mode === "explicit") {
154
- await validateShadeProgression(
155
- "gray",
156
- input.gray.shades,
157
- "gray",
158
- variant,
159
- warnings
160
- );
161
- }
162
- return {
163
- isValid: errors.length === 0,
164
- errors,
165
- warnings
166
- };
80
+ const errors = [];
81
+ const warnings = [];
82
+ const { colors, missingShades } = collectAllColors(input);
83
+ errors.push(...missingShades);
84
+ const chromaticShadeSet = ALL_COLOR_SHADES;
85
+ const grayShadeSet = SHADE_LEVELS;
86
+ for (const group of CHROMATIC_COLOR_GROUPS) {
87
+ const definition = input[group];
88
+ if (definition?.mode === "explicit" && definition.contrastOverrides) {
89
+ for (const shade of Object.keys(definition.contrastOverrides)) if (!chromaticShadeSet.includes(shade)) errors.push({
90
+ field: makeFieldPath(group, shade),
91
+ message: `Invalid contrast override key: ${shade}. Valid keys are: ${ALL_COLOR_SHADES.join(", ")}`
92
+ });
93
+ }
94
+ }
95
+ if (input.gray?.mode === "explicit" && input.gray.contrastOverrides) {
96
+ for (const shade of Object.keys(input.gray.contrastOverrides)) if (!grayShadeSet.includes(shade)) errors.push({
97
+ field: makeFieldPath("gray", shade),
98
+ message: `Invalid contrast override key: ${shade}. Valid keys are: ${SHADE_LEVELS.join(", ")}`
99
+ });
100
+ }
101
+ if (colors.length > 0) {
102
+ const colorMap = {};
103
+ for (const c of colors) colorMap[c.key] = c.color;
104
+ const validationResults = await validateColorsInBatch(colorMap);
105
+ for (const c of colors) if (!validationResults[c.key]) if (c.isContrast) errors.push({
106
+ field: makeFieldPath(c.groupName, `contrast.${c.shade}`),
107
+ message: `Invalid contrast color for shade ${c.shade}: ${c.color}`,
108
+ currentValue: c.color
109
+ });
110
+ else if (c.shade) errors.push({
111
+ field: makeFieldPath(c.groupName, c.shade),
112
+ message: `Invalid color value for shade ${c.shade}: ${c.color}`,
113
+ currentValue: c.color
114
+ });
115
+ else errors.push({
116
+ field: c.groupName,
117
+ message: `Invalid base color: ${c.color}`,
118
+ currentValue: c.color
119
+ });
120
+ }
121
+ for (const group of CHROMATIC_COLOR_GROUPS) {
122
+ const definition = input[group];
123
+ if (definition?.mode === "explicit") {
124
+ await validateShadeProgression(group, definition.shades, "chromatic", variant, warnings);
125
+ await validateMonochromaticHue(group, definition.shades, warnings);
126
+ }
127
+ }
128
+ if (input.gray?.mode === "explicit") await validateShadeProgression("gray", input.gray.shades, "gray", variant, warnings);
129
+ return {
130
+ isValid: errors.length === 0,
131
+ errors,
132
+ warnings
133
+ };
167
134
  }
135
+ /**
136
+ * Format validation result as markdown.
137
+ *
138
+ * This is a thin wrapper around formatValidationMessages for backward compatibility.
139
+ * New code should use formatValidationMessages directly.
140
+ */
168
141
  function formatCustomPaletteValidation(result) {
169
- if (result.isValid && result.warnings.length === 0) return "";
170
- return formatValidationMessages(result);
142
+ if (result.isValid && result.warnings.length === 0) return "";
143
+ return formatValidationMessages(result);
171
144
  }
145
+ /**
146
+ * Validates that shade progression follows expected luminance direction.
147
+ *
148
+ * - Chromatic colors: shade 50 should be lighter than shade 900 (always)
149
+ * - Gray (light themes): shade 50 should be lighter than shade 900
150
+ * - Gray (dark themes): shade 50 should be darker than shade 900 (inverted)
151
+ *
152
+ * Only checks endpoints (50 vs 900), not full progression.
153
+ * Issues warnings, not errors.
154
+ */
172
155
  async function validateShadeProgression(groupName, shades, colorType, variant, warnings) {
173
- const shade50 = shades["50"];
174
- const shade900 = shades["900"];
175
- if (!shade50 || !shade900) {
176
- return;
177
- }
178
- try {
179
- const analysis = await analyzeColorsWithHue({
180
- shade50,
181
- shade900
182
- });
183
- const lum50 = analysis.shade50?.luminance;
184
- const lum900 = analysis.shade900?.luminance;
185
- if (lum50 === void 0 || lum900 === void 0) {
186
- return;
187
- }
188
- const isGrayDarkTheme = colorType === "gray" && variant === "dark";
189
- if (isGrayDarkTheme) {
190
- if (lum50 >= lum900) {
191
- warnings.push({
192
- field: groupName,
193
- message: `For dark themes, gray shade 50 should be darker than shade 900 (inverted progression). Found: 50 (luminance: ${lum50.toFixed(3)}) vs 900 (luminance: ${lum900.toFixed(3)}).`,
194
- severity: "warning"
195
- });
196
- }
197
- } else if (lum50 <= lum900) {
198
- const context = colorType === "gray" ? "For light themes, gray" : "Chromatic";
199
- warnings.push({
200
- field: groupName,
201
- message: `${context} shade 50 should be lighter than shade 900. Found: 50 (luminance: ${lum50.toFixed(3)}) vs 900 (luminance: ${lum900.toFixed(3)}).`,
202
- severity: "warning"
203
- });
204
- }
205
- } catch (_error) {
206
- if (process.env.DEBUG) ;
207
- }
156
+ const shade50 = shades["50"];
157
+ const shade900 = shades["900"];
158
+ if (!shade50 || !shade900) return;
159
+ try {
160
+ const analysis = await analyzeColorsWithHue({
161
+ shade50,
162
+ shade900
163
+ });
164
+ const lum50 = analysis.shade50?.luminance;
165
+ const lum900 = analysis.shade900?.luminance;
166
+ if (lum50 === void 0 || lum900 === void 0) return;
167
+ if (colorType === "gray" && variant === "dark") {
168
+ if (lum50 >= lum900) warnings.push({
169
+ field: groupName,
170
+ message: `For dark themes, gray shade 50 should be darker than shade 900 (inverted progression). Found: 50 (luminance: ${lum50.toFixed(3)}) vs 900 (luminance: ${lum900.toFixed(3)}).`,
171
+ severity: "warning"
172
+ });
173
+ } else if (lum50 <= lum900) {
174
+ const context = colorType === "gray" ? "For light themes, gray" : "Chromatic";
175
+ warnings.push({
176
+ field: groupName,
177
+ message: `${context} shade 50 should be lighter than shade 900. Found: 50 (luminance: ${lum50.toFixed(3)}) vs 900 (luminance: ${lum900.toFixed(3)}).`,
178
+ severity: "warning"
179
+ });
180
+ }
181
+ } catch (_error) {
182
+ if (process.env.DEBUG) {}
183
+ }
208
184
  }
209
- async function validateMonochromaticHue(groupName, shades, warnings, tolerance = DEFAULT_HUE_TOLERANCE) {
210
- const shade50 = shades["50"];
211
- const shade500 = shades["500"];
212
- const shade900 = shades["900"];
213
- if (!shade50 || !shade500 || !shade900) {
214
- return;
215
- }
216
- try {
217
- const analysis = await analyzeColorsWithHue({
218
- shade50,
219
- shade500,
220
- shade900
221
- });
222
- const hue50 = analysis.shade50?.hue;
223
- const hue500 = analysis.shade500?.hue;
224
- const hue900 = analysis.shade900?.hue;
225
- if (hue50 === void 0 || hue500 === void 0 || hue900 === void 0) {
226
- return;
227
- }
228
- const hues = [
229
- { shade: "50", hue: hue50 },
230
- { shade: "500", hue: hue500 },
231
- { shade: "900", hue: hue900 }
232
- ];
233
- const outliers = [];
234
- for (let i = 0; i < hues.length; i++) {
235
- for (let j = i + 1; j < hues.length; j++) {
236
- if (!huesAreClose(hues[i].hue, hues[j].hue, tolerance)) {
237
- outliers.push(
238
- `${hues[i].shade} (${Math.round(hues[i].hue)}°) vs ${hues[j].shade} (${Math.round(hues[j].hue)}°)`
239
- );
240
- }
241
- }
242
- }
243
- if (outliers.length > 0) {
244
- warnings.push({
245
- field: groupName,
246
- message: `Color shades may not be monochromatic (hue varies by more than ±${tolerance}°). Differences found: ${outliers.join(", ")}. Consider using colors from the same hue family for visual consistency.`,
247
- severity: "warning"
248
- });
249
- }
250
- } catch (_error) {
251
- if (process.env.DEBUG) ;
252
- }
185
+ /**
186
+ * Validates that a chromatic color family is monochromatic (same hue family).
187
+ *
188
+ * Checks hues at shades 50, 500, and 900. Warns if hue variation exceeds tolerance.
189
+ * Only applies to chromatic colors, not gray.
190
+ */
191
+ async function validateMonochromaticHue(groupName, shades, warnings, tolerance = 30) {
192
+ const shade50 = shades["50"];
193
+ const shade500 = shades["500"];
194
+ const shade900 = shades["900"];
195
+ if (!shade50 || !shade500 || !shade900) return;
196
+ try {
197
+ const analysis = await analyzeColorsWithHue({
198
+ shade50,
199
+ shade500,
200
+ shade900
201
+ });
202
+ const hue50 = analysis.shade50?.hue;
203
+ const hue500 = analysis.shade500?.hue;
204
+ const hue900 = analysis.shade900?.hue;
205
+ if (hue50 === void 0 || hue500 === void 0 || hue900 === void 0) return;
206
+ const hues = [
207
+ {
208
+ shade: "50",
209
+ hue: hue50
210
+ },
211
+ {
212
+ shade: "500",
213
+ hue: hue500
214
+ },
215
+ {
216
+ shade: "900",
217
+ hue: hue900
218
+ }
219
+ ];
220
+ const outliers = [];
221
+ for (let i = 0; i < hues.length; i++) for (let j = i + 1; j < hues.length; j++) if (!huesAreClose(hues[i].hue, hues[j].hue, tolerance)) outliers.push(`${hues[i].shade} (${Math.round(hues[i].hue)}°) vs ${hues[j].shade} (${Math.round(hues[j].hue)}°)`);
222
+ if (outliers.length > 0) warnings.push({
223
+ field: groupName,
224
+ message: `Color shades may not be monochromatic (hue varies by more than ±${tolerance}°). Differences found: ${outliers.join(", ")}. Consider using colors from the same hue family for visual consistency.`,
225
+ severity: "warning"
226
+ });
227
+ } catch (_error) {
228
+ if (process.env.DEBUG) {}
229
+ }
253
230
  }
254
- export {
255
- formatCustomPaletteValidation,
256
- validateCustomPalette
257
- };
231
+ //#endregion
232
+ export { formatCustomPaletteValidation, validateCustomPalette };
@@ -0,0 +1,3 @@
1
+ import "../utils/result.js";
2
+ import "./custom-palette.js";
3
+ import "./palette.js";