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.
- package/dist/index.d.ts +75 -0
- package/dist/index.js +12 -0
- package/dist/json/components/bootstrap.json +1 -0
- package/dist/json/components/fluent.json +1 -0
- package/dist/json/components/indigo.json +1 -0
- package/dist/json/components/material.json +1 -0
- package/{json → dist/json}/components/themes.json +31 -1
- package/dist/mcp/generators/css.d.ts +7 -4
- package/dist/mcp/generators/css.js +129 -104
- package/dist/mcp/generators/sass.js +227 -254
- package/dist/mcp/index.js +259 -323
- package/dist/mcp/knowledge/color-usage.js +524 -502
- package/dist/mcp/knowledge/colors.js +61 -50
- package/dist/mcp/knowledge/component-metadata.js +697 -598
- package/dist/mcp/knowledge/component-themes.js +70 -57
- package/dist/mcp/knowledge/custom-palettes.js +4 -9
- package/dist/mcp/knowledge/docs/colors/guidance.js +4 -0
- package/dist/mcp/knowledge/docs/colors/usage.js +4 -0
- package/dist/mcp/knowledge/docs/layout/functions/border-radius.js +4 -0
- package/dist/mcp/knowledge/docs/layout/functions/pad.js +4 -0
- package/dist/mcp/knowledge/docs/layout/functions/sizable.js +4 -0
- package/dist/mcp/knowledge/docs/layout/mixins/sizable.js +4 -0
- package/dist/mcp/knowledge/docs/layout/mixins/sizing.js +4 -0
- package/dist/mcp/knowledge/docs/layout/mixins/spacing.js +4 -0
- package/dist/mcp/knowledge/docs/layout/overview.js +4 -0
- package/dist/mcp/knowledge/docs/setup/platform.js +4 -0
- package/dist/mcp/knowledge/elevations.d.ts +1 -1
- package/dist/mcp/knowledge/elevations.js +26 -12
- package/dist/mcp/knowledge/index.js +23 -87
- package/dist/mcp/knowledge/layout-docs.d.ts +1 -1
- package/dist/mcp/knowledge/multipliers.js +5 -0
- package/dist/mcp/knowledge/palettes.js +29 -17
- package/dist/mcp/knowledge/platforms/angular.js +98 -120
- package/dist/mcp/knowledge/platforms/blazor.js +39 -34
- package/dist/mcp/knowledge/platforms/common.js +83 -68
- package/dist/mcp/knowledge/platforms/index.js +265 -242
- package/dist/mcp/knowledge/platforms/react.js +43 -35
- package/dist/mcp/knowledge/platforms/webcomponents.js +266 -292
- package/dist/mcp/knowledge/sass-api.js +1 -0
- package/dist/mcp/knowledge/typography.js +13 -5
- package/dist/mcp/resources/index.js +1 -0
- package/dist/mcp/resources/presets.js +409 -508
- package/dist/mcp/theming/dist/json/colors/meta/multipliers.js +50 -0
- package/dist/mcp/theming/dist/json/colors/presets/palettes.js +85 -0
- package/dist/mcp/theming/dist/json/components/themes.js +5792 -0
- package/dist/mcp/theming/dist/json/elevations/indigo.js +29 -0
- package/dist/mcp/theming/dist/json/elevations/material.js +3 -0
- package/dist/mcp/theming/dist/json/typography/presets/typescales.js +621 -0
- package/dist/mcp/tools/descriptions.js +98 -154
- package/dist/mcp/tools/handlers/color.js +58 -56
- package/dist/mcp/tools/handlers/component-theme.js +163 -225
- package/dist/mcp/tools/handlers/component-tokens.js +159 -219
- package/dist/mcp/tools/handlers/custom-palette.js +138 -179
- package/dist/mcp/tools/handlers/elevations.js +27 -28
- package/dist/mcp/tools/handlers/index.js +11 -0
- package/dist/mcp/tools/handlers/layout.js +125 -176
- package/dist/mcp/tools/handlers/palette.js +105 -120
- package/dist/mcp/tools/handlers/platform.js +289 -311
- package/dist/mcp/tools/handlers/resource.js +22 -31
- package/dist/mcp/tools/handlers/theme.js +86 -103
- package/dist/mcp/tools/handlers/typography.js +29 -30
- package/dist/mcp/tools/index.js +13 -0
- package/dist/mcp/tools/schemas.js +239 -218
- package/dist/mcp/utils/color.js +277 -239
- package/dist/mcp/utils/preprocessing.js +57 -30
- package/dist/mcp/utils/result.js +43 -45
- package/dist/mcp/utils/sass.js +271 -191
- package/dist/mcp/utils/theming-resolve.d.ts +19 -0
- package/dist/mcp/utils/theming-resolve.js +57 -0
- package/dist/mcp/utils/types.js +96 -53
- package/dist/mcp/validators/custom-palette.js +218 -243
- package/dist/mcp/validators/index.js +3 -0
- package/dist/mcp/validators/palette.js +231 -229
- package/dist/tailwind/utilities/bootstrap.css +1 -0
- package/dist/tailwind/utilities/fluent.css +1 -0
- package/dist/tailwind/utilities/indigo.css +1 -0
- package/dist/tailwind/utilities/material.css +1 -0
- package/package.json +45 -64
- package/sass/json/README.md +12 -7
- package/sass/themes/_mixins.scss +1 -0
- package/sass/themes/components/button-group/_button-group-theme.scss +42 -0
- package/sass/themes/components/grid/_grid-theme.scss +1 -1
- package/sass/themes/schemas/components/dark/_button-group.scss +173 -50
- package/sass/themes/schemas/components/dark/_grid.scss +0 -16
- package/sass/themes/schemas/components/light/_button-group.scss +221 -99
- package/sass/themes/schemas/components/light/_grid.scss +14 -20
- package/LICENSE +0 -21
- package/README.md +0 -391
- package/dist/mcp/json/colors/presets/palettes.json.js +0 -13
- package/dist/mcp/json/components/themes.json.js +0 -143
- package/dist/mcp/json/elevations/indigo.json.js +0 -8
- package/dist/mcp/json/elevations/material.json.js +0 -8
- package/dist/mcp/json/typography/presets/typescales.json.js +0 -17
- package/dist/mcp/knowledge/docs/colors/guidance.md.js +0 -4
- package/dist/mcp/knowledge/docs/colors/usage.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/functions/border-radius.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/functions/pad.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/functions/sizable.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/mixins/sizable.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/mixins/sizing.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/mixins/spacing.md.js +0 -4
- package/dist/mcp/knowledge/docs/layout/overview.md.js +0 -4
- package/dist/mcp/knowledge/docs/setup/platform.md.js +0 -4
- package/dist/mcp/vite-env.d.ts +0 -18
- package/index.js +0 -5
- package/json/components/bootstrap.json +0 -1
- package/json/components/fluent.json +0 -1
- package/json/components/indigo.json +0 -1
- package/json/components/material.json +0 -1
- package/tailwind/utilities/bootstrap.css +0 -1
- package/tailwind/utilities/fluent.css +0 -1
- package/tailwind/utilities/indigo.css +0 -1
- package/tailwind/utilities/material.css +0 -1
- /package/{json → dist/json}/colors/meta/multipliers.json +0 -0
- /package/{json → dist/json}/colors/meta/palette.json +0 -0
- /package/{json → dist/json}/colors/presets/palettes.json +0 -0
- /package/{json → dist/json}/elevations/indigo.json +0 -0
- /package/{json → dist/json}/elevations/material.json +0 -0
- /package/{json → dist/json}/typography/presets/typescales.json +0 -0
- /package/{tailwind → dist/tailwind}/themes/base.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/dark/bootstrap.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/dark/fluent.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/dark/indigo.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/dark/material.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/light/bootstrap.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/light/fluent.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/light/indigo.css +0 -0
- /package/{tailwind → dist/tailwind}/themes/light/material.css +0 -0
package/dist/mcp/utils/color.js
CHANGED
|
@@ -1,24 +1,63 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { themingImporter } from "./theming-resolve.js";
|
|
3
2
|
import * as sass from "sass-embedded";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
//#region src/utils/color.ts
|
|
4
|
+
/**
|
|
5
|
+
* Color analysis utilities using Sass-embedded.
|
|
6
|
+
* Calls the actual Sass luminance() and contrast() functions for accurate validation.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Luminance threshold for determining light vs dark colors.
|
|
10
|
+
* Colors with luminance > 0.5 are considered "light".
|
|
11
|
+
* Colors with luminance <= 0.5 are considered "dark".
|
|
12
|
+
*/
|
|
13
|
+
var LUMINANCE_THRESHOLD = .5;
|
|
14
|
+
/**
|
|
15
|
+
* Suggested colors for different variants.
|
|
16
|
+
*/
|
|
17
|
+
var SUGGESTED_COLORS = {
|
|
18
|
+
light: {
|
|
19
|
+
surface: [
|
|
20
|
+
"white",
|
|
21
|
+
"#ffffff",
|
|
22
|
+
"#f8f9fa",
|
|
23
|
+
"#fafafa",
|
|
24
|
+
"#f5f5f5"
|
|
25
|
+
],
|
|
26
|
+
gray: [
|
|
27
|
+
"black",
|
|
28
|
+
"#000000",
|
|
29
|
+
"#333333",
|
|
30
|
+
"#212121",
|
|
31
|
+
"#424242"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
dark: {
|
|
35
|
+
surface: [
|
|
36
|
+
"#222222",
|
|
37
|
+
"#1a1a1a",
|
|
38
|
+
"#121212",
|
|
39
|
+
"#181818",
|
|
40
|
+
"#2d2d2d"
|
|
41
|
+
],
|
|
42
|
+
gray: [
|
|
43
|
+
"white",
|
|
44
|
+
"#ffffff",
|
|
45
|
+
"#e5e5e5",
|
|
46
|
+
"#f5f5f5",
|
|
47
|
+
"#eeeeee"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
18
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Analyze a single color using Sass luminance() function.
|
|
53
|
+
*
|
|
54
|
+
* @param color - CSS color value (hex, rgb, hsl, or named color)
|
|
55
|
+
* @returns Color analysis with luminance and isLight flag
|
|
56
|
+
* @throws Error if Sass compilation fails or color is invalid
|
|
57
|
+
*/
|
|
19
58
|
async function analyzeColor(color) {
|
|
20
|
-
|
|
21
|
-
@use 'sass/color' as color;
|
|
59
|
+
const sassCode = `
|
|
60
|
+
@use 'igniteui-theming/sass/color' as color;
|
|
22
61
|
|
|
23
62
|
$lum: color.luminance(${color});
|
|
24
63
|
|
|
@@ -26,237 +65,236 @@ $lum: color.luminance(${color});
|
|
|
26
65
|
--luminance: #{$lum};
|
|
27
66
|
}
|
|
28
67
|
`;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
luminance,
|
|
43
|
-
isLight: luminance > LUMINANCE_THRESHOLD
|
|
44
|
-
};
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
-
throw new Error(`Failed to analyze color "${color}": ${message}`);
|
|
48
|
-
}
|
|
68
|
+
try {
|
|
69
|
+
const luminanceMatch = (await sass.compileStringAsync(sassCode, { importers: [themingImporter] })).css.match(/--luminance:\s*([\d.]+)/);
|
|
70
|
+
if (!luminanceMatch) throw new Error(`Could not parse luminance from Sass output for color: ${color}`);
|
|
71
|
+
const luminance = Number.parseFloat(luminanceMatch[1]);
|
|
72
|
+
return {
|
|
73
|
+
color,
|
|
74
|
+
luminance,
|
|
75
|
+
isLight: luminance > LUMINANCE_THRESHOLD
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
throw new Error(`Failed to analyze color "${color}": ${message}`);
|
|
80
|
+
}
|
|
49
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Analyze surface and gray colors together in a single Sass compilation.
|
|
84
|
+
* This is more efficient than calling analyzeColor twice.
|
|
85
|
+
*
|
|
86
|
+
* @param params - Object containing surface and/or gray colors
|
|
87
|
+
* @returns Combined analysis results
|
|
88
|
+
*/
|
|
50
89
|
async function analyzeSurfaceGrayColors(params) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
analysis.gray = {
|
|
98
|
-
color: gray,
|
|
99
|
-
luminance,
|
|
100
|
-
isLight: luminance > LUMINANCE_THRESHOLD
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (surface && gray) {
|
|
105
|
-
const contrastMatch = result.css.match(/--contrast-ratio:\s*([\d.]+)/);
|
|
106
|
-
if (contrastMatch) {
|
|
107
|
-
analysis.contrastRatio = Number.parseFloat(contrastMatch[1]);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return analysis;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
-
throw new Error(`Failed to analyze surface/gray colors: ${message}`);
|
|
114
|
-
}
|
|
90
|
+
const { surface, gray } = params;
|
|
91
|
+
if (!surface && !gray) return {};
|
|
92
|
+
const sassLines = [`@use 'igniteui-theming/sass/color' as color;`, ""];
|
|
93
|
+
if (surface) sassLines.push(`$surface-lum: color.luminance(${surface});`);
|
|
94
|
+
if (gray) sassLines.push(`$gray-lum: color.luminance(${gray});`);
|
|
95
|
+
if (surface && gray) sassLines.push(`$contrast: color.contrast(${surface}, ${gray});`);
|
|
96
|
+
sassLines.push("", ":root {");
|
|
97
|
+
if (surface) sassLines.push(" --surface-luminance: #{$surface-lum};");
|
|
98
|
+
if (gray) sassLines.push(" --gray-luminance: #{$gray-lum};");
|
|
99
|
+
if (surface && gray) sassLines.push(" --contrast-ratio: #{$contrast};");
|
|
100
|
+
sassLines.push("}");
|
|
101
|
+
const sassCode = sassLines.join("\n");
|
|
102
|
+
try {
|
|
103
|
+
const result = await sass.compileStringAsync(sassCode, { importers: [themingImporter] });
|
|
104
|
+
const analysis = {};
|
|
105
|
+
if (surface) {
|
|
106
|
+
const surfaceMatch = result.css.match(/--surface-luminance:\s*([\d.]+)/);
|
|
107
|
+
if (surfaceMatch) {
|
|
108
|
+
const luminance = Number.parseFloat(surfaceMatch[1]);
|
|
109
|
+
analysis.surface = {
|
|
110
|
+
color: surface,
|
|
111
|
+
luminance,
|
|
112
|
+
isLight: luminance > LUMINANCE_THRESHOLD
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (gray) {
|
|
117
|
+
const grayMatch = result.css.match(/--gray-luminance:\s*([\d.]+)/);
|
|
118
|
+
if (grayMatch) {
|
|
119
|
+
const luminance = Number.parseFloat(grayMatch[1]);
|
|
120
|
+
analysis.gray = {
|
|
121
|
+
color: gray,
|
|
122
|
+
luminance,
|
|
123
|
+
isLight: luminance > LUMINANCE_THRESHOLD
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (surface && gray) {
|
|
128
|
+
const contrastMatch = result.css.match(/--contrast-ratio:\s*([\d.]+)/);
|
|
129
|
+
if (contrastMatch) analysis.contrastRatio = Number.parseFloat(contrastMatch[1]);
|
|
130
|
+
}
|
|
131
|
+
return analysis;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
throw new Error(`Failed to analyze surface/gray colors: ${message}`);
|
|
135
|
+
}
|
|
115
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if a color is valid by attempting to analyze it.
|
|
139
|
+
*
|
|
140
|
+
* @param color - CSS color value to validate
|
|
141
|
+
* @returns true if the color is valid, false otherwise
|
|
142
|
+
*/
|
|
116
143
|
async function isValidColor(color) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
try {
|
|
145
|
+
await analyzeColor(color);
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
123
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Validate multiple colors in a single Sass compilation for efficiency.
|
|
153
|
+
* This is much faster than calling isValidColor() for each color individually.
|
|
154
|
+
*
|
|
155
|
+
* @param colors - Map of key names to color values
|
|
156
|
+
* @returns Map of key names to validation results (true = valid, false = invalid)
|
|
157
|
+
*/
|
|
124
158
|
async function validateColorsInBatch(colors) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const validationResults = {};
|
|
158
|
-
for (const [key, colorValue] of entries) {
|
|
159
|
-
validationResults[key] = await isValidColor(colorValue);
|
|
160
|
-
}
|
|
161
|
-
return validationResults;
|
|
162
|
-
}
|
|
159
|
+
const entries = Object.entries(colors);
|
|
160
|
+
if (entries.length === 0) return {};
|
|
161
|
+
const sassLines = [
|
|
162
|
+
`@use 'sass:color';`,
|
|
163
|
+
`@use 'sass:meta';`,
|
|
164
|
+
""
|
|
165
|
+
];
|
|
166
|
+
for (const [key, colorValue] of entries) {
|
|
167
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
168
|
+
sassLines.push(`$${safeKey}-valid: meta.type-of(${colorValue}) == 'color';`);
|
|
169
|
+
}
|
|
170
|
+
sassLines.push("", ":root {");
|
|
171
|
+
for (const [key] of entries) {
|
|
172
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
173
|
+
sassLines.push(` --${safeKey}-valid: #{$${safeKey}-valid};`);
|
|
174
|
+
}
|
|
175
|
+
sassLines.push("}");
|
|
176
|
+
const sassCode = sassLines.join("\n");
|
|
177
|
+
try {
|
|
178
|
+
const result = await sass.compileStringAsync(sassCode, { importers: [themingImporter] });
|
|
179
|
+
const validationResults = {};
|
|
180
|
+
for (const [key] of entries) {
|
|
181
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
182
|
+
const validMatch = result.css.match(new RegExp(`--${safeKey}-valid:\\s*(true|false)`));
|
|
183
|
+
validationResults[key] = validMatch ? validMatch[1] === "true" : false;
|
|
184
|
+
}
|
|
185
|
+
return validationResults;
|
|
186
|
+
} catch {
|
|
187
|
+
const validationResults = {};
|
|
188
|
+
for (const [key, colorValue] of entries) validationResults[key] = await isValidColor(colorValue);
|
|
189
|
+
return validationResults;
|
|
190
|
+
}
|
|
163
191
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Check if two hue values are within tolerance of each other,
|
|
194
|
+
* accounting for the circular nature of hue (0° = 360°).
|
|
195
|
+
*
|
|
196
|
+
* @param hue1 - First hue value (0-360)
|
|
197
|
+
* @param hue2 - Second hue value (0-360)
|
|
198
|
+
* @param tolerance - Maximum allowed difference in degrees (default: 30)
|
|
199
|
+
* @returns true if hues are within tolerance
|
|
200
|
+
*/
|
|
201
|
+
function huesAreClose(hue1, hue2, tolerance = 30) {
|
|
202
|
+
const diff = Math.abs(hue1 - hue2);
|
|
203
|
+
return Math.min(diff, 360 - diff) <= tolerance;
|
|
169
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Analyze multiple colors in a single Sass compilation for efficiency.
|
|
207
|
+
* Returns luminance and hue for each color.
|
|
208
|
+
*
|
|
209
|
+
* @param colors - Map of key names to color values
|
|
210
|
+
* @returns Map of key names to analysis results
|
|
211
|
+
*/
|
|
170
212
|
async function analyzeColorsWithHue(colors) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return analysis;
|
|
212
|
-
} catch (error) {
|
|
213
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
214
|
-
throw new Error(`Failed to analyze colors: ${message}`);
|
|
215
|
-
}
|
|
213
|
+
const entries = Object.entries(colors);
|
|
214
|
+
if (entries.length === 0) return {};
|
|
215
|
+
const sassLines = [
|
|
216
|
+
`@use 'igniteui-theming/sass/color' as igColor;`,
|
|
217
|
+
`@use 'sass:color';`,
|
|
218
|
+
""
|
|
219
|
+
];
|
|
220
|
+
for (const [key, color] of entries) {
|
|
221
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
222
|
+
sassLines.push(`$${safeKey}-lum: igColor.luminance(${color});`);
|
|
223
|
+
sassLines.push(`$${safeKey}-hue: color.channel(${color}, "hue", $space: hsl);`);
|
|
224
|
+
}
|
|
225
|
+
sassLines.push("", ":root {");
|
|
226
|
+
for (const [key] of entries) {
|
|
227
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
228
|
+
sassLines.push(` --${safeKey}-luminance: #{$${safeKey}-lum};`);
|
|
229
|
+
sassLines.push(` --${safeKey}-hue: #{$${safeKey}-hue};`);
|
|
230
|
+
}
|
|
231
|
+
sassLines.push("}");
|
|
232
|
+
const sassCode = sassLines.join("\n");
|
|
233
|
+
try {
|
|
234
|
+
const result = await sass.compileStringAsync(sassCode, { importers: [themingImporter] });
|
|
235
|
+
const analysis = {};
|
|
236
|
+
for (const [key] of entries) {
|
|
237
|
+
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "-");
|
|
238
|
+
const lumMatch = result.css.match(new RegExp(`--${safeKey}-luminance:\\s*([\\d.]+)`));
|
|
239
|
+
const hueMatch = result.css.match(new RegExp(`--${safeKey}-hue:\\s*([\\d.]+)`));
|
|
240
|
+
if (lumMatch && hueMatch) analysis[key] = {
|
|
241
|
+
luminance: Number.parseFloat(lumMatch[1]),
|
|
242
|
+
hue: Number.parseFloat(hueMatch[1])
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return analysis;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
248
|
+
throw new Error(`Failed to analyze colors: ${message}`);
|
|
249
|
+
}
|
|
216
250
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Luminance thresholds for palette shade generation suitability.
|
|
253
|
+
* Colors outside this range may produce poor automatic shade generation results.
|
|
254
|
+
*
|
|
255
|
+
* Note: This is different from LUMINANCE_THRESHOLD (0.5) which determines
|
|
256
|
+
* if a color is "light" or "dark" for variant matching. These thresholds
|
|
257
|
+
* determine if a color can produce a good range of shades when used with
|
|
258
|
+
* the palette() function.
|
|
259
|
+
*
|
|
260
|
+
* Based on color theory research:
|
|
261
|
+
* - Optimal base color tone: 35-65 L* (CIELAB)
|
|
262
|
+
* - Too light (L* > 70-75): darker shades compress together
|
|
263
|
+
* - Too dark (L* < 25-30): lighter shades compress together
|
|
264
|
+
*/
|
|
265
|
+
var PALETTE_LUMINANCE_THRESHOLDS = {
|
|
266
|
+
TOO_DARK: .05,
|
|
267
|
+
TOO_LIGHT: .45
|
|
222
268
|
};
|
|
269
|
+
/**
|
|
270
|
+
* Analyze whether a color is suitable for automatic shade generation.
|
|
271
|
+
* Colors with extreme luminance (very light or very dark) may produce
|
|
272
|
+
* poor results when using the palette() function's automatic shade generation.
|
|
273
|
+
*
|
|
274
|
+
* @param color - CSS color value (hex, rgb, hsl, or named color)
|
|
275
|
+
* @returns Analysis result indicating suitability and any issues
|
|
276
|
+
*/
|
|
223
277
|
async function analyzeColorForPalette(color) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
color,
|
|
245
|
-
luminance: analysis.luminance,
|
|
246
|
-
suitable: true
|
|
247
|
-
};
|
|
278
|
+
const analysis = await analyzeColor(color);
|
|
279
|
+
if (analysis.luminance > PALETTE_LUMINANCE_THRESHOLDS.TOO_LIGHT) return {
|
|
280
|
+
color,
|
|
281
|
+
luminance: analysis.luminance,
|
|
282
|
+
suitable: false,
|
|
283
|
+
issue: "too-light",
|
|
284
|
+
description: `Luminance ${analysis.luminance.toFixed(2)} exceeds ${PALETTE_LUMINANCE_THRESHOLDS.TOO_LIGHT} - darker shades (600-900) will appear washed out`
|
|
285
|
+
};
|
|
286
|
+
if (analysis.luminance < PALETTE_LUMINANCE_THRESHOLDS.TOO_DARK) return {
|
|
287
|
+
color,
|
|
288
|
+
luminance: analysis.luminance,
|
|
289
|
+
suitable: false,
|
|
290
|
+
issue: "too-dark",
|
|
291
|
+
description: `Luminance ${analysis.luminance.toFixed(2)} is below ${PALETTE_LUMINANCE_THRESHOLDS.TOO_DARK} - lighter shades (50-200) will lack contrast range`
|
|
292
|
+
};
|
|
293
|
+
return {
|
|
294
|
+
color,
|
|
295
|
+
luminance: analysis.luminance,
|
|
296
|
+
suitable: true
|
|
297
|
+
};
|
|
248
298
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
DEFAULT_MINIMUM_CONTRAST_RATIO,
|
|
252
|
-
LUMINANCE_THRESHOLD,
|
|
253
|
-
PALETTE_LUMINANCE_THRESHOLDS,
|
|
254
|
-
SUGGESTED_COLORS,
|
|
255
|
-
analyzeColor,
|
|
256
|
-
analyzeColorForPalette,
|
|
257
|
-
analyzeColorsWithHue,
|
|
258
|
-
analyzeSurfaceGrayColors,
|
|
259
|
-
huesAreClose,
|
|
260
|
-
isValidColor,
|
|
261
|
-
validateColorsInBatch
|
|
262
|
-
};
|
|
299
|
+
//#endregion
|
|
300
|
+
export { LUMINANCE_THRESHOLD, SUGGESTED_COLORS, analyzeColorForPalette, analyzeColorsWithHue, analyzeSurfaceGrayColors, huesAreClose, validateColorsInBatch };
|
|
@@ -1,34 +1,61 @@
|
|
|
1
|
+
//#region src/utils/preprocessing.ts
|
|
2
|
+
/**
|
|
3
|
+
* Recursively parse JSON strings in a value.
|
|
4
|
+
*
|
|
5
|
+
* This function handles cases where nested objects are passed as JSON strings
|
|
6
|
+
* (e.g., from MCP Inspector text fields or MCP clients that send nested objects as strings).
|
|
7
|
+
* It only attempts to parse strings that look like JSON objects or arrays (starting with `{` or `[`).
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to process
|
|
10
|
+
* @returns The value with any JSON strings parsed into objects/arrays
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // String that looks like JSON is parsed
|
|
14
|
+
* deepParseJsonStrings('{"mode": "shades"}') // => { mode: 'shades' }
|
|
15
|
+
*
|
|
16
|
+
* // Regular strings are left as-is
|
|
17
|
+
* deepParseJsonStrings('hello') // => 'hello'
|
|
18
|
+
*
|
|
19
|
+
* // Nested objects are processed recursively
|
|
20
|
+
* deepParseJsonStrings({ primary: '{"mode": "shades"}' })
|
|
21
|
+
* // => { primary: { mode: 'shades' } }
|
|
22
|
+
*/
|
|
1
23
|
function deepParseJsonStrings(value) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (Array.isArray(value)) {
|
|
15
|
-
return value.map(deepParseJsonStrings);
|
|
16
|
-
}
|
|
17
|
-
if (value !== null && typeof value === "object") {
|
|
18
|
-
return Object.fromEntries(
|
|
19
|
-
Object.entries(value).map(([k, v]) => [k, deepParseJsonStrings(v)])
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
24
|
+
if (typeof value === "string") {
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) try {
|
|
27
|
+
return deepParseJsonStrings(JSON.parse(trimmed));
|
|
28
|
+
} catch {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) return value.map(deepParseJsonStrings);
|
|
34
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deepParseJsonStrings(v)]));
|
|
35
|
+
return value;
|
|
23
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a tool handler with automatic JSON string preprocessing.
|
|
39
|
+
*
|
|
40
|
+
* This wrapper ensures that nested objects passed as JSON strings (common when
|
|
41
|
+
* using MCP Inspector) are properly parsed before schema validation.
|
|
42
|
+
*
|
|
43
|
+
* @param schema - The Zod schema for validating the tool's parameters
|
|
44
|
+
* @param handler - The tool handler function
|
|
45
|
+
* @returns A wrapped handler that preprocesses inputs before validation
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* server.registerTool(
|
|
49
|
+
* 'create_custom_palette',
|
|
50
|
+
* { ... },
|
|
51
|
+
* withPreprocessing(createCustomPaletteSchema, handleCreateCustomPalette)
|
|
52
|
+
* );
|
|
53
|
+
*/
|
|
24
54
|
function withPreprocessing(schema, handler) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
55
|
+
return async (rawParams) => {
|
|
56
|
+
const preprocessed = deepParseJsonStrings(rawParams);
|
|
57
|
+
return handler(schema.parse(preprocessed));
|
|
58
|
+
};
|
|
30
59
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
withPreprocessing
|
|
34
|
-
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { withPreprocessing };
|