color-value-tools 1.2.0 → 1.2.2

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 (49) hide show
  1. package/README.md +18 -0
  2. package/dist/cjs/a11y.d.ts +40 -0
  3. package/dist/cjs/a11y.js +253 -0
  4. package/dist/cjs/cache.d.ts +9 -0
  5. package/dist/cjs/cache.js +38 -0
  6. package/dist/cjs/convert/format.d.ts +3 -0
  7. package/dist/cjs/convert/format.js +42 -0
  8. package/dist/cjs/convert/index.d.ts +2 -0
  9. package/dist/cjs/convert/index.js +18 -0
  10. package/dist/cjs/convert/spaces.d.ts +189 -0
  11. package/dist/cjs/convert/spaces.js +556 -0
  12. package/dist/cjs/index.d.ts +6 -397
  13. package/dist/cjs/index.js +20 -1450
  14. package/dist/cjs/internal/named-colors.d.ts +1 -0
  15. package/dist/cjs/internal/named-colors.js +165 -0
  16. package/dist/cjs/internal/transfer.d.ts +2 -0
  17. package/dist/cjs/internal/transfer.js +12 -0
  18. package/dist/cjs/manipulate.d.ts +15 -0
  19. package/dist/cjs/manipulate.js +223 -0
  20. package/dist/cjs/palette.d.ts +40 -0
  21. package/dist/cjs/palette.js +164 -0
  22. package/dist/cjs/parse.d.ts +102 -0
  23. package/dist/cjs/parse.js +257 -0
  24. package/dist/cli/{bin/cli.js → cli.js} +28 -26
  25. package/dist/esm/a11y.d.ts +40 -0
  26. package/dist/esm/a11y.js +237 -0
  27. package/dist/esm/cache.d.ts +9 -0
  28. package/dist/esm/cache.js +31 -0
  29. package/dist/esm/convert/format.d.ts +3 -0
  30. package/dist/esm/convert/format.js +37 -0
  31. package/dist/esm/convert/index.d.ts +2 -0
  32. package/dist/esm/convert/index.js +2 -0
  33. package/dist/esm/convert/spaces.d.ts +189 -0
  34. package/dist/esm/convert/spaces.js +520 -0
  35. package/dist/esm/index.d.ts +6 -0
  36. package/dist/esm/index.js +6 -1352
  37. package/dist/esm/internal/named-colors.d.ts +1 -0
  38. package/dist/esm/internal/named-colors.js +162 -0
  39. package/dist/esm/internal/transfer.d.ts +2 -0
  40. package/dist/esm/internal/transfer.js +8 -0
  41. package/dist/esm/manipulate.d.ts +15 -0
  42. package/dist/esm/manipulate.js +210 -0
  43. package/dist/esm/package.json +3 -1
  44. package/dist/esm/palette.d.ts +40 -0
  45. package/dist/esm/palette.js +145 -0
  46. package/dist/esm/parse.d.ts +102 -0
  47. package/dist/esm/parse.js +241 -0
  48. package/package.json +100 -8
  49. package/dist/cli/src/index.js +0 -1452
@@ -0,0 +1,102 @@
1
+ export type ColorType = 'hex' | 'css-var' | 'rgb' | 'hsl' | 'named' | 'oklch' | 'color' | 'unknown';
2
+ export declare function isCssVariable(value: string): boolean;
3
+ export declare function isHexColor(value: string): boolean;
4
+ export declare function isOklchColor(value: string): boolean;
5
+ export declare function isColorFunction(value: string): boolean;
6
+ export declare function isRgbColor(value: string): boolean;
7
+ export declare function isHslColor(value: string): boolean;
8
+ export declare function getColorType(value: string): ColorType;
9
+ export declare function extractCssVariableName(value: string): string;
10
+ export declare function parseCssVar(value: string): {
11
+ variableName: string;
12
+ fallback?: string;
13
+ } | null;
14
+ export declare function parseOklchString(str: string): {
15
+ L: number;
16
+ C: number;
17
+ H: number;
18
+ alpha: number;
19
+ } | null;
20
+ export declare function parseColorFn(str: string): {
21
+ space: string;
22
+ r: number;
23
+ g: number;
24
+ b: number;
25
+ alpha: number;
26
+ } | null;
27
+ export declare function normalizeColor(input: string | {
28
+ r: number;
29
+ g: number;
30
+ b: number;
31
+ } | {
32
+ h: number;
33
+ s: number;
34
+ l: number;
35
+ }): {
36
+ type: string;
37
+ hex: string;
38
+ r: any;
39
+ g: any;
40
+ b: any;
41
+ a: number;
42
+ h: number;
43
+ s: number;
44
+ l: number;
45
+ v: number;
46
+ raw?: undefined;
47
+ } | {
48
+ type: string;
49
+ hex: string;
50
+ r: number;
51
+ g: number;
52
+ b: number;
53
+ a: number;
54
+ h: any;
55
+ s: any;
56
+ l: any;
57
+ v: number;
58
+ raw?: undefined;
59
+ } | {
60
+ type: string;
61
+ hex?: undefined;
62
+ r?: undefined;
63
+ g?: undefined;
64
+ b?: undefined;
65
+ a?: undefined;
66
+ h?: undefined;
67
+ s?: undefined;
68
+ l?: undefined;
69
+ v?: undefined;
70
+ raw?: undefined;
71
+ } | {
72
+ type: string;
73
+ raw: string;
74
+ hex?: undefined;
75
+ r?: undefined;
76
+ g?: undefined;
77
+ b?: undefined;
78
+ a?: undefined;
79
+ h?: undefined;
80
+ s?: undefined;
81
+ l?: undefined;
82
+ v?: undefined;
83
+ } | {
84
+ type: string;
85
+ hex: string;
86
+ r: number;
87
+ g: number;
88
+ b: number;
89
+ a: number;
90
+ h?: undefined;
91
+ s?: undefined;
92
+ l?: undefined;
93
+ v?: undefined;
94
+ raw?: undefined;
95
+ };
96
+ export declare function parseHwbString(str: string): {
97
+ H: number;
98
+ W: number;
99
+ B: number;
100
+ alpha: number;
101
+ } | null;
102
+ export declare function toNearestNamedColor(color: string): string;
@@ -0,0 +1,241 @@
1
+ // Detecting, parsing and normalizing CSS color strings.
2
+ import { NAMED_COLORS } from './internal/named-colors.js';
3
+ import { linearChanToSrgb } from './internal/transfer.js';
4
+ import { displayP3ToRgb, hex8ToRgba, hexToHsl, hexToRgb, hslToRgb, normalizeHex, oklchToRgb, rgbaStringToRgba, rgbToHex, rgbToHsl, rgbToHsv, } from './convert/spaces.js';
5
+ export function isCssVariable(value) {
6
+ return value.trim().startsWith('var(--');
7
+ }
8
+ export function isHexColor(value) {
9
+ const trimmed = value.trim();
10
+ return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(trimmed);
11
+ }
12
+ export function isOklchColor(value) {
13
+ return /^\s*oklcha?\s*\(/i.test(value.trim());
14
+ }
15
+ export function isColorFunction(value) {
16
+ return /^\s*color\s*\(\s*(display-p3|srgb|srgb-linear)\s/i.test(value.trim());
17
+ }
18
+ export function isRgbColor(value) {
19
+ const trimmed = value.trim().toLowerCase();
20
+ return trimmed.startsWith('rgb(') || trimmed.startsWith('rgba(');
21
+ }
22
+ export function isHslColor(value) {
23
+ const trimmed = value.trim().toLowerCase();
24
+ return trimmed.startsWith('hsl(') || trimmed.startsWith('hsla(');
25
+ }
26
+ export function getColorType(value) {
27
+ const trimmed = value.trim();
28
+ const lower = trimmed.toLowerCase();
29
+ if (isCssVariable(trimmed))
30
+ return 'css-var';
31
+ if (isHexColor(trimmed))
32
+ return 'hex';
33
+ if (isRgbColor(trimmed))
34
+ return 'rgb';
35
+ if (isHslColor(trimmed))
36
+ return 'hsl';
37
+ if (isOklchColor(trimmed))
38
+ return 'oklch';
39
+ if (isColorFunction(trimmed))
40
+ return 'color';
41
+ if (lower in NAMED_COLORS)
42
+ return 'named';
43
+ return 'unknown';
44
+ }
45
+ export function extractCssVariableName(value) {
46
+ var _a;
47
+ const match = value.match(/var\(\s*(--[^,)]+)/);
48
+ return ((_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.trim()) || value;
49
+ }
50
+ export function parseCssVar(value) {
51
+ var _a;
52
+ const m = value.trim().match(/^var\(\s*(--[^,)]+)(?:,\s*([\s\S]+?))?\s*\)$/);
53
+ if (!m)
54
+ return null;
55
+ return { variableName: m[1].trim(), fallback: (_a = m[2]) === null || _a === void 0 ? void 0 : _a.trim() };
56
+ }
57
+ // ─── OKLCH / color() CSS string parsing ────────────────────────
58
+ export function parseOklchString(str) {
59
+ const m = str
60
+ .trim()
61
+ .match(/oklcha?\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\s*\)/i);
62
+ if (!m)
63
+ return null;
64
+ let L = parseFloat(m[1]);
65
+ if (m[1].endsWith('%'))
66
+ L = L / 100;
67
+ const C = parseFloat(m[2]);
68
+ const H = parseFloat(m[3]);
69
+ const alpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
70
+ return { L, C, H, alpha };
71
+ }
72
+ export function parseColorFn(str) {
73
+ const m = str
74
+ .trim()
75
+ .match(/color\(\s*([\w-]+)\s+([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+%?)(?:\s*\/\s*([\d.]+))?\s*\)/i);
76
+ if (!m)
77
+ return null;
78
+ const space = m[1].toLowerCase();
79
+ const parseVal = (v) => (v.endsWith('%') ? parseFloat(v) / 100 : parseFloat(v));
80
+ const r = parseVal(m[2]);
81
+ const g = parseVal(m[3]);
82
+ const b = parseVal(m[4]);
83
+ const alpha = m[5] !== undefined ? parseFloat(m[5]) : 1;
84
+ return { space, r, g, b, alpha };
85
+ }
86
+ export function normalizeColor(input) {
87
+ if (typeof input !== 'string') {
88
+ if ('r' in input && 'g' in input && 'b' in input) {
89
+ const { r, g, b } = input;
90
+ const hex = rgbToHex({ r, g, b });
91
+ const [h, s, l] = rgbToHsl({ r, g, b });
92
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
93
+ return { type: 'rgb', hex, r, g, b, a: 1, h, s, l, v: vv };
94
+ }
95
+ if ('h' in input && 's' in input && 'l' in input) {
96
+ const { h, s, l } = input;
97
+ const { r, g, b } = hslToRgb(h, s, l);
98
+ const hex = rgbToHex({ r, g, b });
99
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
100
+ return { type: 'hsl', hex, r, g, b, a: 1, h, s, l, v: vv };
101
+ }
102
+ return { type: 'unknown' };
103
+ }
104
+ const str = input.trim();
105
+ if (isCssVariable(str))
106
+ return { type: 'css-var', raw: str };
107
+ // 8-digit (#rrggbbaa) or 4-digit (#rgba) — check before isHexColor
108
+ const hex8 = str.match(/^#([0-9a-f]{8}|[0-9a-f]{4})$/i);
109
+ if (hex8) {
110
+ const rgba = hex8ToRgba(str);
111
+ if (rgba) {
112
+ const { r, g, b, a } = rgba;
113
+ const hex = rgbToHex({ r, g, b });
114
+ const [h, s, l] = rgbToHsl({ r, g, b });
115
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
116
+ return { type: 'hex', hex, r, g, b, a, h, s, l, v: vv };
117
+ }
118
+ }
119
+ if (isHexColor(str)) {
120
+ const hex = normalizeHex(str);
121
+ const [r, g, b] = hexToRgb(hex);
122
+ const [h, s, l] = hexToHsl(hex);
123
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
124
+ return { type: 'hex', hex, r, g, b, a: 1, h, s, l, v: vv };
125
+ }
126
+ if (isRgbColor(str)) {
127
+ const rgba = rgbaStringToRgba(str);
128
+ if (rgba) {
129
+ const { r, g, b, a } = rgba;
130
+ const hex = rgbToHex({ r, g, b });
131
+ const [h, s, l] = rgbToHsl({ r, g, b });
132
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
133
+ return { type: 'rgb', hex, r, g, b, a, h, s, l, v: vv };
134
+ }
135
+ }
136
+ if (isHslColor(str)) {
137
+ const m = str.match(/hsla?\(([^)]+)\)/i);
138
+ if (m) {
139
+ const parts = m[1].split(/,\s*/);
140
+ const h = parseFloat(parts[0]);
141
+ const s = parseFloat(parts[1]);
142
+ const l = parseFloat(parts[2]);
143
+ const a = parts[3] ? parseFloat(parts[3]) : 1;
144
+ const { r, g, b } = hslToRgb(h, s, l);
145
+ const hex = rgbToHex({ r, g, b });
146
+ const [hh, ss, vv] = rgbToHsv({ r, g, b });
147
+ return { type: 'hsl', hex, r, g, b, a, h, s, l, v: vv };
148
+ }
149
+ }
150
+ if (str === 'transparent')
151
+ return { type: 'named', hex: '#000000', r: 0, g: 0, b: 0, a: 0 };
152
+ const lowerStr = str.toLowerCase();
153
+ if (lowerStr in NAMED_COLORS) {
154
+ const hex = NAMED_COLORS[lowerStr];
155
+ const [r, g, b] = hexToRgb(hex);
156
+ const [h, s, l] = hexToHsl(hex);
157
+ const [, , vv] = rgbToHsv({ r, g, b });
158
+ return { type: 'named', hex, r, g, b, a: 1, h, s, l, v: vv };
159
+ }
160
+ // oklch / oklcha
161
+ if (isOklchColor(str)) {
162
+ const parsed = parseOklchString(str);
163
+ if (parsed) {
164
+ const { r, g, b } = oklchToRgb({ L: parsed.L, C: parsed.C, H: parsed.H });
165
+ const hex = rgbToHex({ r, g, b });
166
+ const [h, s, l] = rgbToHsl({ r, g, b });
167
+ const [, , vv] = rgbToHsv({ r, g, b });
168
+ return { type: 'oklch', hex, r, g, b, a: parsed.alpha, h, s, l, v: vv };
169
+ }
170
+ }
171
+ // color(display-p3 ...) / color(srgb ...)
172
+ if (isColorFunction(str)) {
173
+ const parsed = parseColorFn(str);
174
+ if (parsed) {
175
+ const { r: p3r, g: p3g, b: p3b, alpha } = parsed;
176
+ // Convert from p3/srgb (0-1) to sRGB 0-255
177
+ let r, g, b;
178
+ if (parsed.space === 'display-p3') {
179
+ const srgb = displayP3ToRgb({ r: p3r, g: p3g, b: p3b });
180
+ r = srgb.r;
181
+ g = srgb.g;
182
+ b = srgb.b;
183
+ }
184
+ else if (parsed.space === 'srgb-linear') {
185
+ // srgb-linear components are linear-light, not gamma-encoded — unlike
186
+ // plain srgb below, they need the linear-to-sRGB transfer function
187
+ // applied before scaling to a 0-255 byte. Without this, only the 0/1
188
+ // extremes happened to come out correct; any mid-range value (e.g. 0.5)
189
+ // converted as if it were already gamma-encoded, which is wrong.
190
+ r = Math.round(linearChanToSrgb(p3r) * 255);
191
+ g = Math.round(linearChanToSrgb(p3g) * 255);
192
+ b = Math.round(linearChanToSrgb(p3b) * 255);
193
+ }
194
+ else {
195
+ r = Math.round(Math.max(0, Math.min(255, p3r * 255)));
196
+ g = Math.round(Math.max(0, Math.min(255, p3g * 255)));
197
+ b = Math.round(Math.max(0, Math.min(255, p3b * 255)));
198
+ }
199
+ const hex = rgbToHex({ r, g, b });
200
+ const [h, s, l] = rgbToHsl({ r, g, b });
201
+ const [, , vv] = rgbToHsv({ r, g, b });
202
+ return { type: 'color', hex, r, g, b, a: alpha, h, s, l, v: vv };
203
+ }
204
+ }
205
+ return { type: 'unknown' };
206
+ }
207
+ export function parseHwbString(str) {
208
+ const m = str
209
+ .trim()
210
+ .match(/hwb\(\s*([\d.+-]+)\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+))?\s*\)/i);
211
+ if (!m)
212
+ return null;
213
+ return {
214
+ H: parseFloat(m[1]),
215
+ W: parseFloat(m[2]),
216
+ B: parseFloat(m[3]),
217
+ alpha: m[4] !== undefined ? parseFloat(m[4]) : 1,
218
+ };
219
+ }
220
+ export function toNearestNamedColor(color) {
221
+ var _a, _b, _c;
222
+ const n = normalizeColor(color);
223
+ const r = (_a = n.r) !== null && _a !== void 0 ? _a : 0, g = (_b = n.g) !== null && _b !== void 0 ? _b : 0, b = (_c = n.b) !== null && _c !== void 0 ? _c : 0;
224
+ let bestName = 'black';
225
+ let bestDist = Infinity;
226
+ for (const [name, hex] of Object.entries(NAMED_COLORS)) {
227
+ // Skip any non-hex entry (currently just a defensive guard — 'transparent'
228
+ // is the only such value left in the table, and it has no real RGB to
229
+ // compare against; hexToRgb() would otherwise silently fall back to
230
+ // normalizeHex()'s placeholder color for it).
231
+ if (hex[0] !== '#')
232
+ continue;
233
+ const [nr, ng, nb] = hexToRgb(hex);
234
+ const dist = Math.sqrt(Math.pow(r - nr, 2) + Math.pow(g - ng, 2) + Math.pow(b - nb, 2));
235
+ if (dist < bestDist) {
236
+ bestDist = dist;
237
+ bestName = name;
238
+ }
239
+ }
240
+ return bestName;
241
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "color-value-tools",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Parse, convert and manipulate colors across hex, RGB, HSL, Lab, LCH, OKLCH and CMYK, with WCAG contrast checks and CSS variables. Zero dependencies.",
5
5
  "author": "macrulez <macrulezru@gmail.com> (https://macrulez.ru/en)",
6
6
  "license": "MIT",
@@ -12,18 +12,108 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/macrulezru/color-value-tools/issues"
14
14
  },
15
+ "type": "commonjs",
15
16
  "main": "dist/cjs/index.js",
16
17
  "module": "dist/esm/index.js",
17
18
  "types": "dist/cjs/index.d.ts",
18
19
  "exports": {
19
20
  ".": {
20
- "types": "./dist/cjs/index.d.ts",
21
- "import": "./dist/esm/index.js",
22
- "require": "./dist/cjs/index.js"
21
+ "import": {
22
+ "types": "./dist/esm/index.d.ts",
23
+ "default": "./dist/esm/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/cjs/index.d.ts",
27
+ "default": "./dist/cjs/index.js"
28
+ }
29
+ },
30
+ "./parse": {
31
+ "import": {
32
+ "types": "./dist/esm/parse.d.ts",
33
+ "default": "./dist/esm/parse.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/cjs/parse.d.ts",
37
+ "default": "./dist/cjs/parse.js"
38
+ }
39
+ },
40
+ "./convert": {
41
+ "import": {
42
+ "types": "./dist/esm/convert/index.d.ts",
43
+ "default": "./dist/esm/convert/index.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/cjs/convert/index.d.ts",
47
+ "default": "./dist/cjs/convert/index.js"
48
+ }
49
+ },
50
+ "./manipulate": {
51
+ "import": {
52
+ "types": "./dist/esm/manipulate.d.ts",
53
+ "default": "./dist/esm/manipulate.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/cjs/manipulate.d.ts",
57
+ "default": "./dist/cjs/manipulate.js"
58
+ }
59
+ },
60
+ "./palette": {
61
+ "import": {
62
+ "types": "./dist/esm/palette.d.ts",
63
+ "default": "./dist/esm/palette.js"
64
+ },
65
+ "require": {
66
+ "types": "./dist/cjs/palette.d.ts",
67
+ "default": "./dist/cjs/palette.js"
68
+ }
69
+ },
70
+ "./a11y": {
71
+ "import": {
72
+ "types": "./dist/esm/a11y.d.ts",
73
+ "default": "./dist/esm/a11y.js"
74
+ },
75
+ "require": {
76
+ "types": "./dist/cjs/a11y.d.ts",
77
+ "default": "./dist/cjs/a11y.js"
78
+ }
79
+ },
80
+ "./cache": {
81
+ "import": {
82
+ "types": "./dist/esm/cache.d.ts",
83
+ "default": "./dist/esm/cache.js"
84
+ },
85
+ "require": {
86
+ "types": "./dist/cjs/cache.d.ts",
87
+ "default": "./dist/cjs/cache.js"
88
+ }
89
+ },
90
+ "./package.json": "./package.json"
91
+ },
92
+ "typesVersions": {
93
+ "*": {
94
+ "parse": [
95
+ "dist/cjs/parse.d.ts"
96
+ ],
97
+ "convert": [
98
+ "dist/cjs/convert/index.d.ts"
99
+ ],
100
+ "manipulate": [
101
+ "dist/cjs/manipulate.d.ts"
102
+ ],
103
+ "palette": [
104
+ "dist/cjs/palette.d.ts"
105
+ ],
106
+ "a11y": [
107
+ "dist/cjs/a11y.d.ts"
108
+ ],
109
+ "cache": [
110
+ "dist/cjs/cache.d.ts"
111
+ ]
23
112
  }
24
113
  },
114
+ "sideEffects": false,
25
115
  "bin": {
26
- "cvt": "./dist/cli/bin/cli.js"
116
+ "cvt": "./dist/cli/cli.js"
27
117
  },
28
118
  "files": [
29
119
  "dist",
@@ -34,9 +124,10 @@
34
124
  "node": ">=18"
35
125
  },
36
126
  "scripts": {
37
- "prepublishOnly": "npm run build",
38
- "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && tsc -p tsconfig.cli.json && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({type:'module'}))\"",
39
- "test": "vitest run"
127
+ "prepare": "npm run build",
128
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && tsc -p tsconfig.cli.json && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({type:'module'},null,2)+'\\n')\"",
129
+ "test": "vitest run",
130
+ "format": "prettier --write ."
40
131
  },
41
132
  "keywords": [
42
133
  "color",
@@ -53,6 +144,7 @@
53
144
  ],
54
145
  "devDependencies": {
55
146
  "@types/node": "^25.5.0",
147
+ "prettier": "^3.9.9",
56
148
  "typescript": "^5.0.0",
57
149
  "vitest": "^2.0.0"
58
150
  }