@tokens-studio/tokenscript-schemas 0.3.5 → 0.3.6
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/package.json +1 -1
- package/src/bundler/selective-bundler.test.ts +16 -6
- package/src/schemas/types/hex-color/from-hsl.tokenscript +124 -0
- package/src/schemas/types/hex-color/from-oklch.tokenscript +119 -0
- package/src/schemas/types/hex-color/from-p3.tokenscript +45 -0
- package/src/schemas/types/hex-color/from-srgb.tokenscript +41 -0
- package/src/schemas/types/hex-color/schema.json +42 -1
- package/src/schemas/types/hex-color/unit.test.ts +245 -0
package/package.json
CHANGED
|
@@ -5,15 +5,20 @@ import { bundleSelectiveSchemas } from "./selective-bundler.js";
|
|
|
5
5
|
const SCHEMAS_DIR = join(process.cwd(), "src/schemas");
|
|
6
6
|
|
|
7
7
|
describe("Selective Bundler", () => {
|
|
8
|
-
it("should bundle a single type schema", async () => {
|
|
8
|
+
it("should bundle a single type schema with its dependencies", async () => {
|
|
9
9
|
const result = await bundleSelectiveSchemas({
|
|
10
10
|
schemas: ["hex-color"],
|
|
11
11
|
schemasDir: SCHEMAS_DIR,
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
expect(result.schemas
|
|
16
|
-
|
|
14
|
+
// hex-color has srgb-color dependency (for .to.hex() conversion)
|
|
15
|
+
expect(result.schemas.length).toBeGreaterThanOrEqual(1);
|
|
16
|
+
|
|
17
|
+
const uris = result.schemas.map((s) => s.uri);
|
|
18
|
+
expect(uris.some((uri) => uri.includes("hex-color"))).toBe(true);
|
|
19
|
+
|
|
20
|
+
const hexSchema = result.schemas.find((s) => s.uri.includes("hex-color"));
|
|
21
|
+
expect(hexSchema?.schema.type).toBe("color");
|
|
17
22
|
expect(result.metadata.requestedSchemas).toEqual(["hex-color"]);
|
|
18
23
|
});
|
|
19
24
|
|
|
@@ -57,7 +62,10 @@ describe("Selective Bundler", () => {
|
|
|
57
62
|
schemasDir: SCHEMAS_DIR,
|
|
58
63
|
});
|
|
59
64
|
|
|
60
|
-
const
|
|
65
|
+
const hexSchemaEntry = result.schemas.find((s) => s.uri.includes("hex-color"));
|
|
66
|
+
expect(hexSchemaEntry).toBeDefined();
|
|
67
|
+
|
|
68
|
+
const hexSchema = hexSchemaEntry!.schema;
|
|
61
69
|
|
|
62
70
|
if (hexSchema.type === "color") {
|
|
63
71
|
// Check that initializer script is inlined (not a file reference)
|
|
@@ -76,7 +84,9 @@ describe("Selective Bundler", () => {
|
|
|
76
84
|
baseUrl: customBaseUrl,
|
|
77
85
|
});
|
|
78
86
|
|
|
79
|
-
|
|
87
|
+
const hexSchemaEntry = result.schemas.find((s) => s.uri.includes("hex-color"));
|
|
88
|
+
expect(hexSchemaEntry).toBeDefined();
|
|
89
|
+
expect(hexSchemaEntry!.uri).toContain(customBaseUrl);
|
|
80
90
|
});
|
|
81
91
|
|
|
82
92
|
it("should include metadata", async () => {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// HSL to Hex Conversion
|
|
2
|
+
// Converts HSL to hexadecimal string format
|
|
3
|
+
// Reference: Standard HSL to RGB algorithm
|
|
4
|
+
//
|
|
5
|
+
// Input: Color.HSL with h (0-360), s (0-1), l (0-1)
|
|
6
|
+
// Output: Hex string #rrggbb
|
|
7
|
+
|
|
8
|
+
// Get input HSL values
|
|
9
|
+
variable h: Number = {input}.h;
|
|
10
|
+
variable s: Number = {input}.s;
|
|
11
|
+
variable l: Number = {input}.l;
|
|
12
|
+
|
|
13
|
+
// Normalize hue to 0-1 range
|
|
14
|
+
variable hue: Number = h / 360;
|
|
15
|
+
|
|
16
|
+
// RGB values (default to achromatic)
|
|
17
|
+
variable r: Number = l;
|
|
18
|
+
variable g: Number = l;
|
|
19
|
+
variable b: Number = l;
|
|
20
|
+
|
|
21
|
+
// Only calculate if there's saturation
|
|
22
|
+
if (s > 0) [
|
|
23
|
+
variable q: Number = 0;
|
|
24
|
+
if (l < 0.5) [
|
|
25
|
+
q = l * (1 + s);
|
|
26
|
+
] else [
|
|
27
|
+
q = l + s - l * s;
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
variable p: Number = 2 * l - q;
|
|
31
|
+
|
|
32
|
+
// Red (hue + 1/3)
|
|
33
|
+
variable tr: Number = hue + 0.333333333333333;
|
|
34
|
+
if (tr < 0) [ tr = tr + 1; ];
|
|
35
|
+
if (tr > 1) [ tr = tr - 1; ];
|
|
36
|
+
|
|
37
|
+
if (tr < 0.166666666666667) [
|
|
38
|
+
r = p + (q - p) * 6 * tr;
|
|
39
|
+
] else [
|
|
40
|
+
if (tr < 0.5) [
|
|
41
|
+
r = q;
|
|
42
|
+
] else [
|
|
43
|
+
if (tr < 0.666666666666667) [
|
|
44
|
+
r = p + (q - p) * (0.666666666666667 - tr) * 6;
|
|
45
|
+
] else [
|
|
46
|
+
r = p;
|
|
47
|
+
];
|
|
48
|
+
];
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Green (hue)
|
|
52
|
+
variable tg: Number = hue;
|
|
53
|
+
if (tg < 0) [ tg = tg + 1; ];
|
|
54
|
+
if (tg > 1) [ tg = tg - 1; ];
|
|
55
|
+
|
|
56
|
+
if (tg < 0.166666666666667) [
|
|
57
|
+
g = p + (q - p) * 6 * tg;
|
|
58
|
+
] else [
|
|
59
|
+
if (tg < 0.5) [
|
|
60
|
+
g = q;
|
|
61
|
+
] else [
|
|
62
|
+
if (tg < 0.666666666666667) [
|
|
63
|
+
g = p + (q - p) * (0.666666666666667 - tg) * 6;
|
|
64
|
+
] else [
|
|
65
|
+
g = p;
|
|
66
|
+
];
|
|
67
|
+
];
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Blue (hue - 1/3)
|
|
71
|
+
variable tb: Number = hue - 0.333333333333333;
|
|
72
|
+
if (tb < 0) [ tb = tb + 1; ];
|
|
73
|
+
if (tb > 1) [ tb = tb - 1; ];
|
|
74
|
+
|
|
75
|
+
if (tb < 0.166666666666667) [
|
|
76
|
+
b = p + (q - p) * 6 * tb;
|
|
77
|
+
] else [
|
|
78
|
+
if (tb < 0.5) [
|
|
79
|
+
b = q;
|
|
80
|
+
] else [
|
|
81
|
+
if (tb < 0.666666666666667) [
|
|
82
|
+
b = p + (q - p) * (0.666666666666667 - tb) * 6;
|
|
83
|
+
] else [
|
|
84
|
+
b = p;
|
|
85
|
+
];
|
|
86
|
+
];
|
|
87
|
+
];
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Convert RGB to hex
|
|
91
|
+
variable hex: String = "#";
|
|
92
|
+
variable value: Number = 0;
|
|
93
|
+
|
|
94
|
+
// Red
|
|
95
|
+
value = round(r * 255);
|
|
96
|
+
if (value < 0) [ value = 0; ];
|
|
97
|
+
if (value > 255) [ value = 255; ];
|
|
98
|
+
if (value < 16) [
|
|
99
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
100
|
+
] else [
|
|
101
|
+
hex = hex.concat(value.to_string(16));
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// Green
|
|
105
|
+
value = round(g * 255);
|
|
106
|
+
if (value < 0) [ value = 0; ];
|
|
107
|
+
if (value > 255) [ value = 255; ];
|
|
108
|
+
if (value < 16) [
|
|
109
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
110
|
+
] else [
|
|
111
|
+
hex = hex.concat(value.to_string(16));
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
// Blue
|
|
115
|
+
value = round(b * 255);
|
|
116
|
+
if (value < 0) [ value = 0; ];
|
|
117
|
+
if (value > 255) [ value = 255; ];
|
|
118
|
+
if (value < 16) [
|
|
119
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
120
|
+
] else [
|
|
121
|
+
hex = hex.concat(value.to_string(16));
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
return hex;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// OKLCH to Hex Conversion
|
|
2
|
+
// Converts OKLCH perceptual color to hexadecimal string format
|
|
3
|
+
// Path: OKLCH → OKLab → XYZ-D65 → Linear sRGB → sRGB → Hex
|
|
4
|
+
//
|
|
5
|
+
// Input: Color.OKLCH with l (0-1), c, h (0-360)
|
|
6
|
+
// Output: Hex string #rrggbb
|
|
7
|
+
|
|
8
|
+
// Get input OKLCH values
|
|
9
|
+
variable ok_l: Number = {input}.l;
|
|
10
|
+
variable ok_c: Number = {input}.c;
|
|
11
|
+
variable ok_h: Number = {input}.h;
|
|
12
|
+
|
|
13
|
+
// === Step 1: OKLCH to OKLab (polar to cartesian) ===
|
|
14
|
+
variable pi: Number = pi();
|
|
15
|
+
variable deg_to_rad: Number = pi / 180;
|
|
16
|
+
variable h_rad: Number = ok_h * deg_to_rad;
|
|
17
|
+
|
|
18
|
+
variable lab_a: Number = ok_c * cos(h_rad);
|
|
19
|
+
variable lab_b: Number = ok_c * sin(h_rad);
|
|
20
|
+
|
|
21
|
+
// === Step 2: OKLab to XYZ-D65 ===
|
|
22
|
+
// Inverse Lab-to-LMS matrix
|
|
23
|
+
variable lms_l: Number = 1.0 * ok_l + 0.3963377773761749 * lab_a + 0.2158037573099136 * lab_b;
|
|
24
|
+
variable lms_m: Number = 1.0 * ok_l + -0.1055613458156586 * lab_a + -0.0638541728258133 * lab_b;
|
|
25
|
+
variable lms_s: Number = 1.0 * ok_l + -0.0894841775298119 * lab_a + -1.2914855480194092 * lab_b;
|
|
26
|
+
|
|
27
|
+
// Cube the values (inverse of cube root)
|
|
28
|
+
variable lms_l_cubed: Number = lms_l * lms_l * lms_l;
|
|
29
|
+
variable lms_m_cubed: Number = lms_m * lms_m * lms_m;
|
|
30
|
+
variable lms_s_cubed: Number = lms_s * lms_s * lms_s;
|
|
31
|
+
|
|
32
|
+
// Inverse LMS-to-XYZ matrix
|
|
33
|
+
variable xyz_x: Number = 1.2268798758459243 * lms_l_cubed + -0.5578149944602171 * lms_m_cubed + 0.2813910456659647 * lms_s_cubed;
|
|
34
|
+
variable xyz_y: Number = -0.0405757452148008 * lms_l_cubed + 1.1122868032803170 * lms_m_cubed + -0.0717110580655164 * lms_s_cubed;
|
|
35
|
+
variable xyz_z: Number = -0.0763729366746601 * lms_l_cubed + -0.4214933324022432 * lms_m_cubed + 1.5869240198367816 * lms_s_cubed;
|
|
36
|
+
|
|
37
|
+
// === Step 3: XYZ-D65 to Linear sRGB ===
|
|
38
|
+
variable linear_r: Number = 3.2409699419045226 * xyz_x + -1.537383177570094 * xyz_y + -0.4986107602930034 * xyz_z;
|
|
39
|
+
variable linear_g: Number = -0.9692436362808796 * xyz_x + 1.8759675015077202 * xyz_y + 0.04155505740717559 * xyz_z;
|
|
40
|
+
variable linear_b: Number = 0.05563007969699366 * xyz_x + -0.20397695888897652 * xyz_y + 1.0569715142428786 * xyz_z;
|
|
41
|
+
|
|
42
|
+
// === Step 4: Linear sRGB to sRGB (gamma correction) ===
|
|
43
|
+
variable threshold: Number = 0.0031308;
|
|
44
|
+
variable linear_scale: Number = 12.92;
|
|
45
|
+
variable gamma_offset: Number = 0.055;
|
|
46
|
+
variable gamma_scale: Number = 1.055;
|
|
47
|
+
variable gamma_exp: Number = 0.416666666666667;
|
|
48
|
+
|
|
49
|
+
variable srgb_r: Number = 0;
|
|
50
|
+
if (linear_r <= threshold) [
|
|
51
|
+
srgb_r = linear_r * linear_scale;
|
|
52
|
+
] else [
|
|
53
|
+
if (linear_r > 0) [
|
|
54
|
+
srgb_r = gamma_scale * pow(linear_r, gamma_exp) - gamma_offset;
|
|
55
|
+
] else [
|
|
56
|
+
srgb_r = 0;
|
|
57
|
+
];
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
variable srgb_g: Number = 0;
|
|
61
|
+
if (linear_g <= threshold) [
|
|
62
|
+
srgb_g = linear_g * linear_scale;
|
|
63
|
+
] else [
|
|
64
|
+
if (linear_g > 0) [
|
|
65
|
+
srgb_g = gamma_scale * pow(linear_g, gamma_exp) - gamma_offset;
|
|
66
|
+
] else [
|
|
67
|
+
srgb_g = 0;
|
|
68
|
+
];
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
variable srgb_b: Number = 0;
|
|
72
|
+
if (linear_b <= threshold) [
|
|
73
|
+
srgb_b = linear_b * linear_scale;
|
|
74
|
+
] else [
|
|
75
|
+
if (linear_b > 0) [
|
|
76
|
+
srgb_b = gamma_scale * pow(linear_b, gamma_exp) - gamma_offset;
|
|
77
|
+
] else [
|
|
78
|
+
srgb_b = 0;
|
|
79
|
+
];
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// === Step 5: sRGB to Hex ===
|
|
83
|
+
variable hex: String = "#";
|
|
84
|
+
variable value: Number = 0;
|
|
85
|
+
|
|
86
|
+
// Red (clamp to 0-1)
|
|
87
|
+
value = srgb_r;
|
|
88
|
+
if (value < 0) [ value = 0; ];
|
|
89
|
+
if (value > 1) [ value = 1; ];
|
|
90
|
+
value = round(value * 255);
|
|
91
|
+
if (value < 16) [
|
|
92
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
93
|
+
] else [
|
|
94
|
+
hex = hex.concat(value.to_string(16));
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Green
|
|
98
|
+
value = srgb_g;
|
|
99
|
+
if (value < 0) [ value = 0; ];
|
|
100
|
+
if (value > 1) [ value = 1; ];
|
|
101
|
+
value = round(value * 255);
|
|
102
|
+
if (value < 16) [
|
|
103
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
104
|
+
] else [
|
|
105
|
+
hex = hex.concat(value.to_string(16));
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
// Blue
|
|
109
|
+
value = srgb_b;
|
|
110
|
+
if (value < 0) [ value = 0; ];
|
|
111
|
+
if (value > 1) [ value = 1; ];
|
|
112
|
+
value = round(value * 255);
|
|
113
|
+
if (value < 16) [
|
|
114
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
115
|
+
] else [
|
|
116
|
+
hex = hex.concat(value.to_string(16));
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
return hex;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Display P3 to Hex Conversion
|
|
2
|
+
// Converts P3 (0-1) to hexadecimal string format
|
|
3
|
+
// Note: P3 colors may be out of sRGB gamut, values are clamped to 0-1
|
|
4
|
+
//
|
|
5
|
+
// Examples:
|
|
6
|
+
// P3(1, 0, 0) → #ff0000
|
|
7
|
+
// P3(0, 1, 0.5) → #00ff80
|
|
8
|
+
|
|
9
|
+
variable hex: String = "#";
|
|
10
|
+
variable value: Number = 0;
|
|
11
|
+
|
|
12
|
+
// Red channel (clamp P3 to sRGB range)
|
|
13
|
+
value = {input}.r;
|
|
14
|
+
if (value < 0) [ value = 0; ];
|
|
15
|
+
if (value > 1) [ value = 1; ];
|
|
16
|
+
value = round(value * 255);
|
|
17
|
+
if (value < 16) [
|
|
18
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
19
|
+
] else [
|
|
20
|
+
hex = hex.concat(value.to_string(16));
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Green channel
|
|
24
|
+
value = {input}.g;
|
|
25
|
+
if (value < 0) [ value = 0; ];
|
|
26
|
+
if (value > 1) [ value = 1; ];
|
|
27
|
+
value = round(value * 255);
|
|
28
|
+
if (value < 16) [
|
|
29
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
30
|
+
] else [
|
|
31
|
+
hex = hex.concat(value.to_string(16));
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Blue channel
|
|
35
|
+
value = {input}.b;
|
|
36
|
+
if (value < 0) [ value = 0; ];
|
|
37
|
+
if (value > 1) [ value = 1; ];
|
|
38
|
+
value = round(value * 255);
|
|
39
|
+
if (value < 16) [
|
|
40
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
41
|
+
] else [
|
|
42
|
+
hex = hex.concat(value.to_string(16));
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return hex;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// sRGB to Hex Conversion
|
|
2
|
+
// Converts sRGB (0-1) to hexadecimal string format
|
|
3
|
+
//
|
|
4
|
+
// Examples:
|
|
5
|
+
// sRGB(1, 0, 0) → #ff0000
|
|
6
|
+
// sRGB(0, 1, 0.5) → #00ff80
|
|
7
|
+
|
|
8
|
+
variable hex: String = "#";
|
|
9
|
+
variable value: Number = 0;
|
|
10
|
+
|
|
11
|
+
// Red channel
|
|
12
|
+
value = round({input}.r * 255);
|
|
13
|
+
if (value < 0) [ value = 0; ];
|
|
14
|
+
if (value > 255) [ value = 255; ];
|
|
15
|
+
if (value < 16) [
|
|
16
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
17
|
+
] else [
|
|
18
|
+
hex = hex.concat(value.to_string(16));
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Green channel
|
|
22
|
+
value = round({input}.g * 255);
|
|
23
|
+
if (value < 0) [ value = 0; ];
|
|
24
|
+
if (value > 255) [ value = 255; ];
|
|
25
|
+
if (value < 16) [
|
|
26
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
27
|
+
] else [
|
|
28
|
+
hex = hex.concat(value.to_string(16));
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// Blue channel
|
|
32
|
+
value = round({input}.b * 255);
|
|
33
|
+
if (value < 0) [ value = 0; ];
|
|
34
|
+
if (value > 255) [ value = 255; ];
|
|
35
|
+
if (value < 16) [
|
|
36
|
+
hex = hex.concat("0").concat(value.to_string(16));
|
|
37
|
+
] else [
|
|
38
|
+
hex = hex.concat(value.to_string(16));
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
return hex;
|
|
@@ -20,5 +20,46 @@
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
|
-
"conversions": [
|
|
23
|
+
"conversions": [
|
|
24
|
+
{
|
|
25
|
+
"source": "/api/v1/core/srgb-color/0/",
|
|
26
|
+
"target": "$self",
|
|
27
|
+
"description": "Converts sRGB (0-1) to Hex format",
|
|
28
|
+
"lossless": true,
|
|
29
|
+
"script": {
|
|
30
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
31
|
+
"script": "./from-srgb.tokenscript"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"source": "/api/v1/core/p3-color/0/",
|
|
36
|
+
"target": "$self",
|
|
37
|
+
"description": "Converts Display P3 to Hex format (clamps to sRGB gamut)",
|
|
38
|
+
"lossless": false,
|
|
39
|
+
"script": {
|
|
40
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
41
|
+
"script": "./from-p3.tokenscript"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"source": "/api/v1/core/hsl-color/0/",
|
|
46
|
+
"target": "$self",
|
|
47
|
+
"description": "Converts HSL to Hex format",
|
|
48
|
+
"lossless": true,
|
|
49
|
+
"script": {
|
|
50
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
51
|
+
"script": "./from-hsl.tokenscript"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"source": "/api/v1/core/oklch-color/0/",
|
|
56
|
+
"target": "$self",
|
|
57
|
+
"description": "Converts OKLCH to Hex format (clamps to sRGB gamut)",
|
|
58
|
+
"lossless": false,
|
|
59
|
+
"script": {
|
|
60
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
61
|
+
"script": "./from-oklch.tokenscript"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
24
65
|
}
|
|
@@ -138,4 +138,249 @@ describe("Hex Color Schema", () => {
|
|
|
138
138
|
|
|
139
139
|
// Note: Conversion tests are in rgb-color/unit.test.ts since RGB owns the conversions
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
describe("Conversions", () => {
|
|
143
|
+
it("should convert sRGB to hex (red)", async () => {
|
|
144
|
+
const result = await executeWithSchema(
|
|
145
|
+
"hex-color",
|
|
146
|
+
"type",
|
|
147
|
+
`
|
|
148
|
+
variable color: Color.SRGB;
|
|
149
|
+
color.r = 1; color.g = 0; color.b = 0;
|
|
150
|
+
color.to.hex()
|
|
151
|
+
`,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect((result as any).value).toBe("#ff0000");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should convert sRGB to hex (green)", async () => {
|
|
158
|
+
const result = await executeWithSchema(
|
|
159
|
+
"hex-color",
|
|
160
|
+
"type",
|
|
161
|
+
`
|
|
162
|
+
variable color: Color.SRGB;
|
|
163
|
+
color.r = 0; color.g = 1; color.b = 0;
|
|
164
|
+
color.to.hex()
|
|
165
|
+
`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect((result as any).value).toBe("#00ff00");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should convert sRGB to hex (blue)", async () => {
|
|
172
|
+
const result = await executeWithSchema(
|
|
173
|
+
"hex-color",
|
|
174
|
+
"type",
|
|
175
|
+
`
|
|
176
|
+
variable color: Color.SRGB;
|
|
177
|
+
color.r = 0; color.g = 0; color.b = 1;
|
|
178
|
+
color.to.hex()
|
|
179
|
+
`,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect((result as any).value).toBe("#0000ff");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should convert sRGB to hex (mid values)", async () => {
|
|
186
|
+
const result = await executeWithSchema(
|
|
187
|
+
"hex-color",
|
|
188
|
+
"type",
|
|
189
|
+
`
|
|
190
|
+
variable color: Color.SRGB;
|
|
191
|
+
color.r = 0.5; color.g = 0.5; color.b = 0.5;
|
|
192
|
+
color.to.hex()
|
|
193
|
+
`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// 0.5 * 255 = 127.5, rounds to 128 = 0x80
|
|
197
|
+
expect((result as any).value).toBe("#808080");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should convert sRGB to hex (black)", async () => {
|
|
201
|
+
const result = await executeWithSchema(
|
|
202
|
+
"hex-color",
|
|
203
|
+
"type",
|
|
204
|
+
`
|
|
205
|
+
variable color: Color.SRGB;
|
|
206
|
+
color.r = 0; color.g = 0; color.b = 0;
|
|
207
|
+
color.to.hex()
|
|
208
|
+
`,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect((result as any).value).toBe("#000000");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should convert sRGB to hex (white)", async () => {
|
|
215
|
+
const result = await executeWithSchema(
|
|
216
|
+
"hex-color",
|
|
217
|
+
"type",
|
|
218
|
+
`
|
|
219
|
+
variable color: Color.SRGB;
|
|
220
|
+
color.r = 1; color.g = 1; color.b = 1;
|
|
221
|
+
color.to.hex()
|
|
222
|
+
`,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect((result as any).value).toBe("#ffffff");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// P3 to Hex conversions
|
|
229
|
+
it("should convert P3 to hex (red)", async () => {
|
|
230
|
+
const result = await executeWithSchema(
|
|
231
|
+
"hex-color",
|
|
232
|
+
"type",
|
|
233
|
+
`
|
|
234
|
+
variable color: Color.P3;
|
|
235
|
+
color.r = 1; color.g = 0; color.b = 0;
|
|
236
|
+
color.to.hex()
|
|
237
|
+
`,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
expect((result as any).value).toBe("#ff0000");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should convert P3 to hex (clamps out-of-gamut)", async () => {
|
|
244
|
+
const result = await executeWithSchema(
|
|
245
|
+
"hex-color",
|
|
246
|
+
"type",
|
|
247
|
+
`
|
|
248
|
+
variable color: Color.P3;
|
|
249
|
+
color.r = 1.2; color.g = -0.1; color.b = 0.5;
|
|
250
|
+
color.to.hex()
|
|
251
|
+
`,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// 1.2 clamps to 1 (ff), -0.1 clamps to 0 (00), 0.5 = 80
|
|
255
|
+
expect((result as any).value).toBe("#ff0080");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// HSL to Hex conversions
|
|
259
|
+
it("should convert HSL to hex (red)", async () => {
|
|
260
|
+
const result = await executeWithSchema(
|
|
261
|
+
"hex-color",
|
|
262
|
+
"type",
|
|
263
|
+
`
|
|
264
|
+
variable color: Color.HSL;
|
|
265
|
+
color.h = 0; color.s = 1; color.l = 0.5;
|
|
266
|
+
color.to.hex()
|
|
267
|
+
`,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect((result as any).value).toBe("#ff0000");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should convert HSL to hex (green)", async () => {
|
|
274
|
+
const result = await executeWithSchema(
|
|
275
|
+
"hex-color",
|
|
276
|
+
"type",
|
|
277
|
+
`
|
|
278
|
+
variable color: Color.HSL;
|
|
279
|
+
color.h = 120; color.s = 1; color.l = 0.5;
|
|
280
|
+
color.to.hex()
|
|
281
|
+
`,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect((result as any).value).toBe("#00ff00");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should convert HSL to hex (blue)", async () => {
|
|
288
|
+
const result = await executeWithSchema(
|
|
289
|
+
"hex-color",
|
|
290
|
+
"type",
|
|
291
|
+
`
|
|
292
|
+
variable color: Color.HSL;
|
|
293
|
+
color.h = 240; color.s = 1; color.l = 0.5;
|
|
294
|
+
color.to.hex()
|
|
295
|
+
`,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect((result as any).value).toBe("#0000ff");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("should convert HSL to hex (gray - no saturation)", async () => {
|
|
302
|
+
const result = await executeWithSchema(
|
|
303
|
+
"hex-color",
|
|
304
|
+
"type",
|
|
305
|
+
`
|
|
306
|
+
variable color: Color.HSL;
|
|
307
|
+
color.h = 0; color.s = 0; color.l = 0.5;
|
|
308
|
+
color.to.hex()
|
|
309
|
+
`,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect((result as any).value).toBe("#808080");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// OKLCH to Hex conversions
|
|
316
|
+
it("should convert OKLCH to hex (red-ish)", async () => {
|
|
317
|
+
const result = await executeWithSchema(
|
|
318
|
+
"hex-color",
|
|
319
|
+
"type",
|
|
320
|
+
`
|
|
321
|
+
variable color: Color.OKLCH;
|
|
322
|
+
color.l = 0.628; color.c = 0.258; color.h = 29;
|
|
323
|
+
color.to.hex()
|
|
324
|
+
`,
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// OKLCH red is approximately l=0.628, c=0.258, h=29
|
|
328
|
+
// Should produce something close to #ff0000
|
|
329
|
+
const hex = (result as any).value as string;
|
|
330
|
+
expect(hex).toMatch(/^#[0-9a-f]{6}$/);
|
|
331
|
+
// Red channel should be high
|
|
332
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
333
|
+
expect(r).toBeGreaterThan(200);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("should convert OKLCH to hex (white)", async () => {
|
|
337
|
+
const result = await executeWithSchema(
|
|
338
|
+
"hex-color",
|
|
339
|
+
"type",
|
|
340
|
+
`
|
|
341
|
+
variable color: Color.OKLCH;
|
|
342
|
+
color.l = 1; color.c = 0; color.h = 0;
|
|
343
|
+
color.to.hex()
|
|
344
|
+
`,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
expect((result as any).value).toBe("#ffffff");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should convert OKLCH to hex (black)", async () => {
|
|
351
|
+
const result = await executeWithSchema(
|
|
352
|
+
"hex-color",
|
|
353
|
+
"type",
|
|
354
|
+
`
|
|
355
|
+
variable color: Color.OKLCH;
|
|
356
|
+
color.l = 0; color.c = 0; color.h = 0;
|
|
357
|
+
color.to.hex()
|
|
358
|
+
`,
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect((result as any).value).toBe("#000000");
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should convert OKLCH to hex (gray)", async () => {
|
|
365
|
+
const result = await executeWithSchema(
|
|
366
|
+
"hex-color",
|
|
367
|
+
"type",
|
|
368
|
+
`
|
|
369
|
+
variable color: Color.OKLCH;
|
|
370
|
+
color.l = 0.6; color.c = 0; color.h = 0;
|
|
371
|
+
color.to.hex()
|
|
372
|
+
`,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Gray with lightness 0.6 should be mid-gray
|
|
376
|
+
const hex = (result as any).value as string;
|
|
377
|
+
expect(hex).toMatch(/^#[0-9a-f]{6}$/);
|
|
378
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
379
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
380
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
381
|
+
// Should be achromatic (r ≈ g ≈ b)
|
|
382
|
+
expect(Math.abs(r - g)).toBeLessThan(2);
|
|
383
|
+
expect(Math.abs(g - b)).toBeLessThan(2);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
141
386
|
});
|