color-value-tools 1.1.3 → 1.1.5

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 (2) hide show
  1. package/README.md +522 -98
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,12 +4,58 @@
4
4
  </h1>
5
5
  <img
6
6
  src="https://s3.twcstorage.ru/c9a2cc89-780f97fd-311d-4a1a-b86f-c25665c9dc46/images/npm/color-value-tools.webp"
7
- alt="vue-virtual-scroller-kit"
7
+ alt="color-value-tools"
8
8
  style="max-width:100%;width:auto;height:300px;border-radius:12px"
9
9
  />
10
10
  </div>
11
11
 
12
- A comprehensive utility library for parsing, converting, manipulating, and analyzing color values across all major color models — hex, RGB, HSL, HSV, HWB, Lab, LCH, OKLAB, OKLCH, CMYK — plus CSS variables and named colors.
12
+ A comprehensive utility library for parsing, converting, manipulating, and analyzing color values across all major color models — hex, RGB, HSL, HSV, HWB, Lab, LCH, Oklab, Oklch, CMYK, Display P3 — plus CSS variables and named colors. Zero dependencies.
13
+
14
+ ---
15
+
16
+ ## Contents
17
+
18
+ - [Features](#features)
19
+ - [Installation](#installation)
20
+ - [Quick start](#quick-start)
21
+ - [Detection](#detection)
22
+ - [Parsing & Normalization](#parsing--normalization)
23
+ - [Conversions](#conversions)
24
+ - [Manipulation](#manipulation)
25
+ - [Interpolation & Scales](#interpolation--scales)
26
+ - [Color Harmonies](#color-harmonies)
27
+ - [Palette Generation](#palette-generation)
28
+ - [Accessibility (WCAG)](#accessibility-wcag)
29
+ - [Color Blindness Simulation](#color-blindness-simulation)
30
+ - [Formatting](#formatting)
31
+ - [Cache Management](#cache-management)
32
+ - [Generator Functions](#generator-functions)
33
+ - [CLI](#cli)
34
+ - [Cookbook](#cookbook)
35
+ - [TypeScript types](#typescript-types)
36
+ - [Architecture](#architecture)
37
+ - [Bundle size & dependencies](#bundle-size--dependencies)
38
+
39
+ ---
40
+
41
+ ## Features
42
+
43
+ - **Universal parser** — `normalizeColor()` accepts hex (3/4/6/8-digit), `rgb()` / `rgba()`, `hsl()` / `hsla()`, `hwb()`, `oklch()` / `oklcha()`, `color(display-p3 ...)`, CSS named colors (all 148), and plain `{r,g,b}` / `{h,s,l}` objects
44
+ - **Conversions** — every path between hex, RGB, HSL, HSV, HWB, Lab, LCH, Oklab, Oklch, CMYK, Display P3; all round-trip accurate
45
+ - **Manipulation** — `lighten`, `darken`, `saturate`, `desaturate`, `invertColor`, `grayscale`, `setAlpha`, `rotateHue`, `adjustHexBrightness`
46
+ - **Mixing & Interpolation** — `mixColors` and `interpolateColors` in 6 color spaces with 4 hue interpolation modes; `createColorScale` across multiple anchor colors with custom positions; `midpointColor` for a perceptual midpoint
47
+ - **Color Harmonies** — `complement`, `triadic`, `analogous`, `splitComplementary`, `tetradic`
48
+ - **Palette Generation** — `colorShades`, `monochromatic`, `tints`, `shades`, `tones` — all Oklab-accurate where applicable
49
+ - **WCAG Accessibility** — `relativeLuminance`, `contrastRatio`, `wcagLevel`, `bestTextColor`, `bestContrastColor`, `bestContrastPalette`, `isReadableOnBackground` (solid, semi-transparent, and gradient backgrounds)
50
+ - **Color Blindness Simulation** — protanopia, deuteranopia, tritanopia using Vienot 1999 matrices on linear RGB
51
+ - **Perceptual color distance** — `colorDeltaE` implementing the full CIEDE2000 formula
52
+ - **CSS Formatting** — `toHslString`, `toHwbString`, `toOklchString`, `toColorP3String`
53
+ - **Cache** — LRU-style `normalizeColorCached`; `getCacheStats`, `clearColorCache`, `enableCache` / `disableCache`
54
+ - **Generator functions** — `generateGradientColors*`, `generateTints*`, `generateShades*` for lazy iteration without pre-allocating arrays
55
+ - **CLI (`cvt`)** — inspect any color from the terminal: full info, all conversions, WCAG contrast, shades, harmonies, nearest named color
56
+ - **Zero dependencies** — no runtime deps; ships as tree-shakeable ESM + CommonJS
57
+
58
+ ---
13
59
 
14
60
  ## Installation
15
61
 
@@ -17,7 +63,11 @@ A comprehensive utility library for parsing, converting, manipulating, and analy
17
63
  npm install color-value-tools
18
64
  ```
19
65
 
20
- ## Usage
66
+ No peer dependencies required.
67
+
68
+ ---
69
+
70
+ ## Quick start
21
71
 
22
72
  ```ts
23
73
  import {
@@ -26,70 +76,100 @@ import {
26
76
  complement, triadic,
27
77
  wcagLevel, bestTextColor,
28
78
  colorDeltaE, randomColor,
29
- } from 'color-value-tools';
79
+ } from 'color-value-tools'
30
80
 
31
81
  // Parse any color format
32
- normalizeColor('#3498db');
33
- // { type: 'hex', hex: '#3498db', r: 52, g: 152, b: 219, h: 204, s: 70, l: 53, ... }
82
+ normalizeColor('#3498db')
83
+ // { type: 'hex', hex: '#3498db', r: 52, g: 152, b: 219, h: 204, s: 70, l: 53, v: 86, a: 1 }
34
84
 
35
85
  // Manipulate
36
- lighten('#3498db', 15); // '#6ab4e8'
37
- darken('#3498db', 15); // '#1a6da3'
38
- saturate('#3498db', 20); // '#1a8fe8'
86
+ lighten('#3498db', 15) // '#6ab4e8'
87
+ darken('#3498db', 15) // '#1a6da3'
88
+ saturate('#3498db', 20) // '#1a8fe8'
39
89
 
40
90
  // Harmonies
41
- complement('#3498db'); // '#db6034'
42
- triadic('#3498db'); // ['#3498db', '#db3498', '#98db34']
91
+ complement('#3498db') // '#db6034'
92
+ triadic('#3498db') // ['#3498db', '#db3498', '#98db34']
43
93
 
44
94
  // WCAG accessibility
45
- wcagLevel('#ffffff', '#3498db'); // 'AA'
46
- bestTextColor('#3498db'); // '#ffffff'
95
+ wcagLevel('#ffffff', '#3498db') // 'AA'
96
+ bestTextColor('#3498db') // '#ffffff'
47
97
 
48
98
  // Perceptual color distance (CIEDE2000)
49
- colorDeltaE('#ff0000', '#fe0000'); // ~0.9
99
+ colorDeltaE('#ff0000', '#fe0000') // ~0.9
50
100
 
51
101
  // Random color
52
- randomColor({ hRange: [200, 260], sRange: [60, 80] });
102
+ randomColor({ hRange: [200, 260], sRange: [60, 80] })
53
103
  ```
54
104
 
55
105
  CommonJS:
106
+
56
107
  ```js
57
- const { normalizeColor, mixColors } = require('color-value-tools');
108
+ const { normalizeColor, mixColors } = require('color-value-tools')
58
109
  ```
59
110
 
60
111
  ---
61
112
 
62
- ## API Reference
63
-
64
- ### Detection
113
+ ## Detection
65
114
 
66
115
  | Function | Description |
67
116
  |---|---|
68
117
  | `getColorType(value)` | Returns `'hex'` \| `'css-var'` \| `'rgb'` \| `'hsl'` \| `'named'` \| `'oklch'` \| `'color'` \| `'unknown'` |
69
- | `isHexColor(value)` | Detects 3-, 4-, 6- or 8-digit hex strings (with or without `#`) |
118
+ | `isHexColor(value)` | Detects 3-, 4-, or 6-digit hex strings (with or without `#`) |
70
119
  | `isRgbColor(value)` | Detects `rgb()` / `rgba()` strings |
71
120
  | `isHslColor(value)` | Detects `hsl()` / `hsla()` strings |
72
121
  | `isOklchColor(value)` | Detects `oklch()` / `oklcha()` strings |
73
- | `isColorFunction(value)` | Detects `color(display-p3 ...)` / `color(srgb ...)` strings |
122
+ | `isColorFunction(value)` | Detects `color(display-p3 ...)`, `color(srgb ...)`, `color(srgb-linear ...)` strings |
74
123
  | `isCssVariable(value)` | Checks for `var(--name)` pattern |
75
124
  | `extractCssVariableName(value)` | Extracts `--name` from `var(--name, fallback)` |
76
125
 
77
- ### Parsing & Normalization
126
+ ---
127
+
128
+ ## Parsing & Normalization
78
129
 
79
130
  | Function | Description |
80
131
  |---|---|
81
- | `normalizeColor(input)` | Universal parser. Accepts hex, `rgb()`, `hsl()`, `oklch()`, `color()`, named color, `{r,g,b}` / `{h,s,l}`. Returns `{ type, hex, r, g, b, a, h, s, l, v }` |
82
- | `normalizeColorCached(input)` | Same as `normalizeColor` but uses an internal LRU-style cache |
132
+ | `normalizeColor(input)` | Universal parser. Accepts hex (3/4/6/8-digit), `rgb()`, `hsl()`, `hwb()`, `oklch()`, `color()`, named color, `{r,g,b}` object, `{h,s,l}` object. Returns `{ type, hex, r, g, b, a, h, s, l, v }` |
133
+ | `normalizeColorCached(input)` | Same as `normalizeColor` but uses an internal LRU-style cache. String input only |
83
134
  | `normalizeHex(hex)` | Normalizes 3- or 6-digit hex to lowercase 6-digit with `#` |
84
- | `rgbaStringToRgba(str)` | Parses `rgb()` / `rgba()` string to `{r, g, b, a}` |
85
- | `hex8ToRgba(hex)` | Parses 8-digit hex (`#rrggbbaa`) to `{r, g, b, a}` |
135
+ | `rgbaStringToRgba(str)` | Parses `rgb()` / `rgba()` string to `{r, g, b, a}`; supports percentage channels |
136
+ | `hex8ToRgba(hex)` | Parses 8-digit hex (`#rrggbbaa`) or 4-digit hex (`#rgba`) to `{r, g, b, a}` |
86
137
  | `shortHexToRgba(hex)` | Parses 4-digit hex (`#rgba`) to `{r, g, b, a}` — e.g. `#f0f0` → `{r:255,g:0,b:255,a:0}` |
87
138
  | `parseHwbString(str)` | Parses `hwb()` string to `{H, W, B, alpha}` |
88
- | `parseOklchString(str)` | Parses `oklch(L C H / alpha)` to `{L, C, H, alpha}` |
139
+ | `parseOklchString(str)` | Parses `oklch(L C H / alpha)` to `{L, C, H, alpha}`; accepts percentage L |
89
140
  | `parseColorFn(str)` | Parses `color(display-p3 r g b / alpha)` to `{space, r, g, b, alpha}` |
90
141
  | `parseCssVar(value)` | Parses `var(--name, fallback)` to `{variableName, fallback?}` |
91
142
 
92
- ### Conversions
143
+ ### `normalizeColor` return value
144
+
145
+ ```ts
146
+ {
147
+ type: 'hex' | 'rgb' | 'hsl' | 'oklch' | 'color' | 'named' | 'css-var' | 'unknown'
148
+ hex?: string // '#rrggbb'
149
+ r?: number // 0–255
150
+ g?: number // 0–255
151
+ b?: number // 0–255
152
+ a?: number // 0–1
153
+ h?: number // hue 0–360
154
+ s?: number // HSL saturation 0–100
155
+ l?: number // HSL lightness 0–100
156
+ v?: number // HSV value 0–100
157
+ raw?: string // set for css-var type
158
+ }
159
+ ```
160
+
161
+ ```ts
162
+ // Object input
163
+ normalizeColor({ r: 52, g: 152, b: 219 }) // → full result with hex, h, s, l, v
164
+ normalizeColor({ h: 204, s: 70, l: 53 }) // → full result with hex, r, g, b, v
165
+
166
+ // CSS variable — returns raw, no color channels
167
+ normalizeColor('var(--brand-color)') // → { type: 'css-var', raw: 'var(--brand-color)' }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Conversions
93
173
 
94
174
  | Function | Description |
95
175
  |---|---|
@@ -103,6 +183,7 @@ const { normalizeColor, mixColors } = require('color-value-tools');
103
183
  | `hsvToRgb(h, s, v)` | `→ {r, g, b}` |
104
184
  | `rgbToHex({r,g,b})` | `→ #rrggbb` |
105
185
  | `rgbaToHex({r,g,b,a})` | `→ #rrggbbaa` |
186
+ | `rgbaToHex8({r,g,b,a})` | Alias for `rgbaToHex` |
106
187
  | `rgbToRgbaString({r,g,b}, a)` | `→ rgba(...)` string |
107
188
  | `rgbToHsl({r,g,b})` | `→ [h, s, l]` |
108
189
  | `rgbToHsv({r,g,b})` | `→ [h, s, v]` |
@@ -121,9 +202,10 @@ const { normalizeColor, mixColors } = require('color-value-tools');
121
202
  | `rgbToDisplayP3({r,g,b})` | `→ {r, g, b}` in Display P3 space (0–1 per channel) |
122
203
  | `displayP3ToRgb({r,g,b})` | `→ {r, g, b}` sRGB (0–255) |
123
204
  | `toDisplayP3Hex(color)` | Converts any color → Display P3 → hex |
124
- | `rgbaToHex8({r,g,b,a})` | Alias for `rgbaToHex` |
125
205
 
126
- ### Manipulation
206
+ ---
207
+
208
+ ## Manipulation
127
209
 
128
210
  | Function | Description |
129
211
  |---|---|
@@ -133,21 +215,64 @@ const { normalizeColor, mixColors } = require('color-value-tools');
133
215
  | `desaturate(color, amount)` | Decrease HSL saturation by `amount` (0–100) |
134
216
  | `setAlpha(color, alpha)` | Returns `rgba(...)` with the given alpha (0–1) |
135
217
  | `getAlpha(color)` | Returns the alpha channel value (0–1) |
136
- | `invertColor(color)` | Inverts RGB channels |
137
- | `grayscale(color)` | Converts to grayscale using ITU-R BT.709 weights |
138
- | `rotateHue(hex, degrees)` | Rotates hue by degrees (supports negative values) |
218
+ | `invertColor(color)` | Inverts all three RGB channels |
219
+ | `grayscale(color)` | Converts to grayscale using ITU-R BT.709 perceptual weights |
220
+ | `rotateHue(hex, degrees)` | Rotates hue by degrees; supports negative values |
139
221
  | `adjustHexBrightness(hex, offsetPercent)` | Lightens (positive) or darkens (negative) by percentage |
140
- | `mixColors(c1, c2, t, opts?)` | Interpolates between two colors. `t` = 0–1. `mode`: `'rgb'`\|`'hsl'`\|`'lab'`\|`'lch'`\|`'oklab'`\|`'oklch'`. `hueInterpolation`: `'shorter'`\|`'longer'`\|`'increasing'`\|`'decreasing'` |
222
+ | `mixColors(c1, c2, t, opts?)` | Interpolates between two colors. `t` = 0–1. `mode`: `'rgb'` \| `'hsl'` \| `'lab'` \| `'lch'` \| `'oklab'` \| `'oklch'`. `hueInterpolation`: `'shorter'` \| `'longer'` \| `'increasing'` \| `'decreasing'` |
223
+
224
+ ### `mixColors` example
141
225
 
142
- ### Interpolation & Scales
226
+ ```ts
227
+ import { mixColors } from 'color-value-tools'
228
+
229
+ // RGB mix at midpoint
230
+ mixColors('#e74c3c', '#3498db', 0.5)
231
+ // → '#8dc6bc'
232
+
233
+ // Perceptually even mix in Oklab
234
+ mixColors('#e74c3c', '#3498db', 0.5, { mode: 'oklab', format: 'hex' })
235
+
236
+ // HSL with "longer" hue path
237
+ mixColors('#e74c3c', '#3498db', 0.5, { mode: 'hsl', hueInterpolation: 'longer' })
238
+
239
+ // Get rgba string output
240
+ mixColors('#ff0000', '#0000ff', 0.25, { mode: 'lab', format: 'rgba' })
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Interpolation & Scales
143
246
 
144
247
  | Function | Description |
145
248
  |---|---|
146
- | `interpolateColors(c1, c2, steps, opts?)` | Returns array of `steps` colors from `c1` to `c2`. Same `space`/`format`/`hueInterpolation` options as `mixColors` |
249
+ | `interpolateColors(c1, c2, steps, opts?)` | Returns array of `steps` colors from `c1` to `c2`. Same `space` / `format` / `hueInterpolation` options as `mixColors` |
147
250
  | `createColorScale(anchors, steps, opts?)` | Generates a `steps`-color scale across multiple anchor colors with optional positions (0–1) |
148
251
  | `midpointColor(c1, c2, opts?)` | Perceptual midpoint between two colors (default space: `'oklab'`) |
149
252
 
150
- ### Color Harmonies
253
+ ### `createColorScale` — multi-stop gradient
254
+
255
+ ```ts
256
+ import { createColorScale } from 'color-value-tools'
257
+
258
+ // Evenly spaced anchors
259
+ createColorScale(['#ff0000', '#ffff00', '#00ff00'], 9)
260
+
261
+ // With explicit positions
262
+ createColorScale(
263
+ [
264
+ { color: '#1a1a2e', position: 0 },
265
+ { color: '#e94560', position: 0.4 },
266
+ { color: '#f5a623', position: 1 },
267
+ ],
268
+ 12,
269
+ { space: 'oklch' }
270
+ )
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Color Harmonies
151
276
 
152
277
  | Function | Description |
153
278
  |---|---|
@@ -157,7 +282,17 @@ const { normalizeColor, mixColors } = require('color-value-tools');
157
282
  | `splitComplementary(color)` | Base + two colors at 150° and 210° |
158
283
  | `tetradic(color)` | 4 colors evenly spaced 90° apart |
159
284
 
160
- ### Palette Generation
285
+ ```ts
286
+ import { triadic, analogous, tetradic } from 'color-value-tools'
287
+
288
+ triadic('#6c3483') // ['#6c3483', '#34836c', '#83346c']
289
+ analogous('#6c3483', 45) // ['#833469', '#6c3483', '#3469…']
290
+ tetradic('#6c3483') // 4 colors
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Palette Generation
161
296
 
162
297
  | Function | Description |
163
298
  |---|---|
@@ -167,7 +302,9 @@ const { normalizeColor, mixColors } = require('color-value-tools');
167
302
  | `shades(color, steps?)` | Mix toward black in Oklab (default 5 steps) |
168
303
  | `tones(color, steps?, gray?)` | Mix toward gray in Oklab (default 5 steps, gray `#808080`) |
169
304
 
170
- ### Accessibility (WCAG)
305
+ ---
306
+
307
+ ## Accessibility (WCAG)
171
308
 
172
309
  | Function | Description |
173
310
  |---|---|
@@ -176,14 +313,56 @@ const { normalizeColor, mixColors } = require('color-value-tools');
176
313
  | `wcagLevel(fg, bg)` | Returns `'AAA'` \| `'AA'` \| `'AA-large'` \| `'fail'` |
177
314
  | `bestTextColor(bg)` | Returns `'#000000'` or `'#ffffff'` for best contrast on `bg` |
178
315
  | `bestContrastColor(bg, candidates)` | Picks the most readable color from an array of candidates |
179
- | `bestContrastPalette(bg, palettes, opts?)` | Picks the palette with best overall contrast. Returns `{paletteIndex, palette, minContrastRatio, avgContrastRatio}` |
180
- | `isReadableOnBackground(text, bg, opts?)` | Checks readability on solid, semi-transparent, or gradient backgrounds. Returns `{readable, minContrastRatio, wcagLevel}` |
316
+ | `bestContrastPalette(bg, palettes, opts?)` | Picks the palette with best overall contrast. Returns `{ paletteIndex, palette, minContrastRatio, avgContrastRatio }` |
317
+ | `isReadableOnBackground(text, bg, opts?)` | Checks readability on solid, semi-transparent, or gradient backgrounds. Returns `{ readable, minContrastRatio, wcagLevel }` |
181
318
  | `isDark(color, threshold?)` | `true` if luminance is below threshold (default 0.5) |
182
319
  | `isLight(color, threshold?)` | Inverse of `isDark` |
183
320
 
184
- ### Color Blindness Simulation
321
+ ### `isReadableOnBackground` complex backgrounds
322
+
323
+ `background` can be a plain color string, a semi-transparent spec, or a gradient with multiple stops:
324
+
325
+ ```ts
326
+ import { isReadableOnBackground } from 'color-value-tools'
327
+
328
+ // Solid background
329
+ isReadableOnBackground('#ffffff', '#3498db')
330
+ // → { readable: true, minContrastRatio: 3.05, wcagLevel: 'AA-large' }
331
+
332
+ // Semi-transparent overlay composited over white
333
+ isReadableOnBackground('#ffffff', {
334
+ type: 'semi-transparent',
335
+ color: 'rgba(52, 152, 219, 0.5)',
336
+ underlay: '#f0f0f0',
337
+ })
338
+
339
+ // Gradient — worst stop is used for the result
340
+ isReadableOnBackground('#ffffff', {
341
+ type: 'gradient',
342
+ stops: ['#1a1a2e', '#e94560', '#f5a623'],
343
+ })
344
+
345
+ // AAA + large text
346
+ isReadableOnBackground('#000000', '#f0f0f0', { level: 'AAA', largeText: true })
347
+ ```
348
+
349
+ ### `bestContrastPalette` — pick accessible palette
350
+
351
+ ```ts
352
+ import { bestContrastPalette } from 'color-value-tools'
353
+
354
+ const result = bestContrastPalette('#1a1a2e', [
355
+ ['#ffffff', '#f0f0f0', '#cccccc'],
356
+ ['#ffff00', '#ffd700', '#ff8c00'],
357
+ ])
358
+ // → { paletteIndex: 0, palette: [...], minContrastRatio: 12.1, avgContrastRatio: 14.3 }
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Color Blindness Simulation
185
364
 
186
- Simulates perception using Vienot 1999 matrices applied to linear RGB.
365
+ Simulates perception using Vienot 1999 matrices applied to linearized RGB.
187
366
 
188
367
  | Function | Description |
189
368
  |---|---|
@@ -192,7 +371,16 @@ Simulates perception using Vienot 1999 matrices applied to linear RGB.
192
371
  | `simulateTritanopia(color)` | No S-cones (blue-blind) |
193
372
  | `simulateColorBlindness(color, type)` | Generic — `type`: `'protanopia'` \| `'deuteranopia'` \| `'tritanopia'` |
194
373
 
195
- ### Formatting
374
+ ```ts
375
+ import { simulateColorBlindness } from 'color-value-tools'
376
+
377
+ simulateColorBlindness('#e74c3c', 'deuteranopia') // '#9a7b00'
378
+ simulateColorBlindness('#3498db', 'protanopia') // '#5282db'
379
+ ```
380
+
381
+ ---
382
+
383
+ ## Formatting
196
384
 
197
385
  | Function | Description |
198
386
  |---|---|
@@ -201,48 +389,81 @@ Simulates perception using Vienot 1999 matrices applied to linear RGB.
201
389
  | `toOklchString(color, alpha?)` | Converts any color to `oklch(L C H)` CSS string |
202
390
  | `toColorP3String(color, alpha?)` | Converts any color to `color(display-p3 r g b)` CSS string |
203
391
 
204
- ### Cache Management
392
+ ```ts
393
+ import { toOklchString, toColorP3String, toHwbString } from 'color-value-tools'
205
394
 
206
- | Function | Description |
207
- |---|---|
208
- | `normalizeColorCached(input)` | Cached version of `normalizeColor` for repeated calls |
209
- | `clearColorCache()` | Clears the normalization cache |
210
- | `getCacheStats()` | Returns `{size, hits}` |
211
- | `enableCache()` / `disableCache()` | Toggle caching on/off |
395
+ toOklchString('#3498db') // 'oklch(0.6 0.1175 234.77)'
396
+ toColorP3String('#3498db') // 'color(display-p3 0.2493 0.5875 0.8479)'
397
+ toHwbString(204, 20, 14) // 'hwb(204 20% 14%)'
398
+ toHwbString(204, 20, 14, 0.8) // 'hwb(204 20% 14% / 0.8)'
399
+ ```
212
400
 
213
- ### Generator Functions
401
+ ---
402
+
403
+ ## Cache Management
214
404
 
215
- Useful for large palettes and frame-by-frame animations without allocating full arrays.
405
+ Useful in rendering loops, color pickers, or any context with repeated `normalizeColor` calls on the same values.
216
406
 
217
407
  | Function | Description |
218
408
  |---|---|
219
- | `generateGradientColors*(start, end, steps, opts?)` | Yields `steps` colors from `start` to `end` |
220
- | `generateTints*(color, steps, opts?)` | Yields tints toward white |
221
- | `generateShades*(color, steps, opts?)` | Yields shades toward black |
409
+ | `normalizeColorCached(input)` | Cached version of `normalizeColor` for repeated string calls |
410
+ | `clearColorCache()` | Clears the normalization cache and resets hit counter |
411
+ | `getCacheStats()` | Returns `{ size: number, hits: number }` |
412
+ | `enableCache()` | Re-enables the cache (on by default) |
413
+ | `disableCache()` | Disables caching (useful in tests) |
414
+
415
+ ```ts
416
+ import { normalizeColorCached, getCacheStats, clearColorCache } from 'color-value-tools'
417
+
418
+ // First call — parsed and cached
419
+ normalizeColorCached('#3498db')
222
420
 
223
- ### Utilities
421
+ // Second call — instant cache hit
422
+ normalizeColorCached('#3498db')
423
+
424
+ getCacheStats() // { size: 1, hits: 1 }
425
+ clearColorCache() // { size: 0, hits: 0 } reset
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Generator Functions
431
+
432
+ Useful for large palettes and frame-by-frame animations without allocating full arrays upfront.
224
433
 
225
434
  | Function | Description |
226
435
  |---|---|
227
- | `colorDeltaE(c1, c2)` | Perceptual color distance using CIEDE2000 |
228
- | `randomColor(options?)` | Generates a random color. Options: `hRange`, `sRange`, `lRange` (each `[min, max]`) |
229
- | `toNearestNamedColor(color)` | Returns the closest CSS named color name (all 148 standard colors) |
436
+ | `generateGradientColors*(start, end, steps, opts?)` | Yields `steps` colors from `start` to `end`. Same `mode` / `format` options as `mixColors` |
437
+ | `generateTints*(color, steps, opts?)` | Yields tints toward white in Oklab |
438
+ | `generateShades*(color, steps, opts?)` | Yields shades toward black in Oklab |
439
+
440
+ ```ts
441
+ import { generateGradientColors, generateTints } from 'color-value-tools'
442
+
443
+ // Process one color at a time — no intermediate array
444
+ for (const color of generateGradientColors('#ff0000', '#0000ff', 1000, { mode: 'oklch' })) {
445
+ renderPixel(color)
446
+ }
447
+
448
+ // Collect lazily
449
+ const first5 = [...generateTints('#e74c3c', 100)].slice(0, 5)
450
+ ```
230
451
 
231
452
  ---
232
453
 
233
454
  ## CLI
234
455
 
235
- After installing globally or via `npx`, you can inspect colors directly from the terminal:
456
+ The `cvt` CLI is included in the package. Install globally or use via `npx`:
236
457
 
237
458
  ```bash
238
459
  npm install -g color-value-tools
239
460
  ```
240
461
 
241
462
  ```bash
242
- # Full info (default)
463
+ # Full info (default command)
243
464
  cvt "#3498db"
244
465
 
245
- # All format conversions
466
+ # All format conversions — hex, rgb, hsl, hsv, hwb, lab, lch, oklab, oklch, cmyk
246
467
  cvt "#3498db" convert
247
468
 
248
469
  # Contrast ratio and WCAG level
@@ -251,13 +472,27 @@ cvt "#3498db" contrast "#ffffff"
251
472
  # Generate 7 shades
252
473
  cvt "#3498db" shades 7
253
474
 
254
- # All harmonies
475
+ # All harmonies — complement, triadic, analogous, split-comp, tetradic
255
476
  cvt "#3498db" harmonies
256
477
 
257
478
  # Nearest CSS named color
258
479
  cvt "cornflowerblue" nearest
259
480
  ```
260
481
 
482
+ Accepts any valid color format: hex, `rgb()`, `hsl()`, `oklch()`, named colors.
483
+
484
+ ```
485
+ cvt "#3498db"
486
+ ────────────────────────────────────────
487
+ Type: hex
488
+ Hex: #3498db
489
+ RGB: rgb(52, 152, 219)
490
+ HSL: hsl(204, 70%, 53%)
491
+ HSV: hsv(204, 76%, 86%)
492
+ Nearest named: cornflowerblue
493
+ Best text on it: #ffffff
494
+ ```
495
+
261
496
  ---
262
497
 
263
498
  ## Cookbook
@@ -265,88 +500,277 @@ cvt "cornflowerblue" nearest
265
500
  ### Generate an accessible button palette
266
501
 
267
502
  ```ts
268
- import { colorShades, bestTextColor, wcagLevel } from 'color-value-tools';
503
+ import { colorShades, bestTextColor, wcagLevel } from 'color-value-tools'
269
504
 
270
505
  function buttonPalette(base: string) {
271
- const shades = colorShades(base, 9);
272
- return shades.map(shade => ({
273
- bg: shade,
506
+ return colorShades(base, 9).map(shade => ({
507
+ bg: shade,
274
508
  text: bestTextColor(shade),
275
509
  wcag: wcagLevel(bestTextColor(shade), shade),
276
- }));
510
+ }))
277
511
  }
278
512
 
279
- buttonPalette('#3498db');
513
+ buttonPalette('#3498db')
280
514
  // [{ bg: '#ffffff', text: '#000000', wcag: 'AAA' }, ...]
281
515
  ```
282
516
 
283
- ### Theme-aware dark/light color
517
+ ### Theme-aware color adaptation
284
518
 
285
519
  ```ts
286
- import { isDark, lighten, darken } from 'color-value-tools';
520
+ import { isDark, lighten, darken } from 'color-value-tools'
287
521
 
288
522
  function adaptToTheme(color: string, isDarkTheme: boolean): string {
289
- return isDarkTheme ? lighten(color, 20) : darken(color, 10);
523
+ return isDarkTheme ? lighten(color, 20) : darken(color, 10)
290
524
  }
291
525
  ```
292
526
 
293
- ### Mix two brand colors at a midpoint
527
+ ### Mix two brand colors at a perceptual midpoint
294
528
 
295
529
  ```ts
296
- import { mixColors } from 'color-value-tools';
530
+ import { midpointColor } from 'color-value-tools'
297
531
 
298
- const mid = mixColors('#e74c3c', '#3498db', 0.5, { mode: 'hsl', format: 'hex' });
299
- // Perceptually even blend between red and blue
532
+ const mid = midpointColor('#e74c3c', '#3498db') // Oklab midpoint
533
+ const midLch = midpointColor('#e74c3c', '#3498db', { space: 'oklch' })
300
534
  ```
301
535
 
302
- ### Build a triadic color scheme and check contrast
536
+ ### Build a triadic scheme and check contrast
303
537
 
304
538
  ```ts
305
- import { triadic, contrastRatio } from 'color-value-tools';
539
+ import { triadic, contrastRatio } from 'color-value-tools'
306
540
 
307
- const [base, second, third] = triadic('#6c3483');
308
- console.log(contrastRatio(base, '#ffffff')); // e.g. 8.4
309
- console.log(contrastRatio(second, '#ffffff'));
541
+ const [base, second, third] = triadic('#6c3483')
542
+ console.log(contrastRatio(base, '#ffffff')) // e.g. 8.4
543
+ console.log(contrastRatio(second, '#ffffff'))
310
544
  ```
311
545
 
312
- ### Convert a CSS color string to all formats at once
546
+ ### Convert any color string to all formats at once
313
547
 
314
548
  ```ts
315
- import { normalizeColor, rgbToOklch, rgbToCmyk } from 'color-value-tools';
549
+ import { normalizeColor, rgbToOklch, rgbToCmyk, toColorP3String } from 'color-value-tools'
316
550
 
317
- const n = normalizeColor('hsl(204, 70%, 53%)');
318
- const oklch = rgbToOklch({ r: n.r!, g: n.g!, b: n.b! });
319
- const cmyk = rgbToCmyk({ r: n.r!, g: n.g!, b: n.b! });
320
- console.log(n.hex, oklch, cmyk);
551
+ const n = normalizeColor('hsl(204, 70%, 53%)')
552
+ const oklch = rgbToOklch({ r: n.r!, g: n.g!, b: n.b! })
553
+ const cmyk = rgbToCmyk({ r: n.r!, g: n.g!, b: n.b! })
554
+ const p3 = toColorP3String(n.hex!)
555
+ console.log(n.hex, oklch, cmyk, p3)
321
556
  ```
322
557
 
323
- ### Find the perceptually nearest named CSS color
558
+ ### Find the nearest CSS named color
324
559
 
325
560
  ```ts
326
- import { toNearestNamedColor } from 'color-value-tools';
561
+ import { toNearestNamedColor } from 'color-value-tools'
327
562
 
328
- toNearestNamedColor('#1a8ccc'); // 'steelblue'
563
+ toNearestNamedColor('#1a8ccc') // 'steelblue'
564
+ toNearestNamedColor('#e74c3c') // 'tomato'
329
565
  ```
330
566
 
331
567
  ### Random palette within a hue range
332
568
 
333
569
  ```ts
334
- import { randomColor, colorShades } from 'color-value-tools';
570
+ import { randomColor, colorShades } from 'color-value-tools'
571
+
572
+ const accent = randomColor({ hRange: [200, 260], sRange: [60, 80], lRange: [40, 60] })
573
+ const palette = colorShades(accent, 5)
574
+ ```
575
+
576
+ ### Simulate color blindness for a palette
577
+
578
+ ```ts
579
+ import { triadic, simulateColorBlindness } from 'color-value-tools'
580
+
581
+ const palette = triadic('#e74c3c')
582
+ const deuteranopia = palette.map(c => simulateColorBlindness(c, 'deuteranopia'))
583
+ // Compare palette vs deuteranopia to verify distinguishability
584
+ ```
585
+
586
+ ### Lazy gradient — process 10 000 colors one at a time
587
+
588
+ ```ts
589
+ import { generateGradientColors } from 'color-value-tools'
335
590
 
336
- const accent = randomColor({ hRange: [200, 260], sRange: [60, 80], lRange: [40, 60] });
337
- const palette = colorShades(accent, 5);
591
+ for (const color of generateGradientColors('#1a1a2e', '#f5a623', 10_000, { mode: 'oklch' })) {
592
+ ctx.fillStyle = color
593
+ ctx.fillRect(x++, 0, 1, height)
594
+ }
338
595
  ```
339
596
 
340
597
  ---
341
598
 
342
- ## Author
599
+ ## TypeScript types
343
600
 
344
- Danil Lisin Vladimirovich aka Macrulez
601
+ All public types are exported from the package root:
602
+
603
+ ```ts
604
+ import type {
605
+ ColorType, // 'hex' | 'css-var' | 'rgb' | 'hsl' | 'named' | 'oklch' | 'color' | 'unknown'
606
+ WcagLevel, // 'AAA' | 'AA' | 'AA-large' | 'fail'
607
+ ColorBlindnessType, // 'protanopia' | 'deuteranopia' | 'tritanopia'
608
+ BackgroundSpec, // string | { type: 'semi-transparent'; ... } | { type: 'gradient'; ... }
609
+ PaletteScore, // { palette, minContrastRatio, avgContrastRatio }
610
+ } from 'color-value-tools'
611
+ ```
612
+
613
+ ### `BackgroundSpec` — discriminated union
614
+
615
+ ```ts
616
+ import type { BackgroundSpec } from 'color-value-tools'
617
+
618
+ const solid: BackgroundSpec = '#3498db'
619
+
620
+ const semiTransparent: BackgroundSpec = {
621
+ type: 'semi-transparent',
622
+ color: 'rgba(0,0,0,0.4)',
623
+ underlay: '#ffffff', // optional, defaults to white
624
+ }
625
+
626
+ const gradient: BackgroundSpec = {
627
+ type: 'gradient',
628
+ stops: ['#1a1a2e', '#e94560', '#f5a623'],
629
+ }
630
+ ```
631
+
632
+ ### `PaletteScore` — from `bestContrastPalette`
633
+
634
+ ```ts
635
+ import type { PaletteScore } from 'color-value-tools'
636
+
637
+ const result: PaletteScore & { paletteIndex: number } = bestContrastPalette(bg, palettes)
638
+ result.paletteIndex // number
639
+ result.palette // string[]
640
+ result.minContrastRatio // number
641
+ result.avgContrastRatio // number
642
+ ```
643
+
644
+ ### Inference from `normalizeColor`
645
+
646
+ ```ts
647
+ import { normalizeColor } from 'color-value-tools'
648
+
649
+ const n = normalizeColor('#3498db')
650
+ // TypeScript knows: n.hex, n.r, n.g, n.b, n.h, n.s, n.l, n.v, n.a, n.type
651
+
652
+ if (n.type !== 'unknown' && n.type !== 'css-var') {
653
+ const r: number = n.r! // available for all resolved color types
654
+ }
655
+ ```
656
+
657
+ ---
658
+
659
+ ## Architecture
660
+
661
+ ```
662
+ color-value-tools
663
+
664
+ ├── Detection
665
+ │ getColorType, isHexColor, isRgbColor, isHslColor,
666
+ │ isOklchColor, isColorFunction, isCssVariable
667
+
668
+ ├── Parsing
669
+ │ normalizeColor → universal entry point; handles all formats + objects
670
+ │ rgbaStringToRgba → rgb()/rgba() parser
671
+ │ hex8ToRgba → 8-digit and 4-digit hex with alpha
672
+ │ parseOklchString → oklch(L C H / alpha) with % L support
673
+ │ parseColorFn → color(display-p3 ...) / color(srgb ...)
674
+ │ parseHwbString → hwb() string
675
+ │ parseCssVar → var(--name, fallback)
676
+
677
+ ├── Color Math — every space via RGB as the hub
678
+ │ sRGB ↔ Linear (srgbChanToLinear / linearChanToSrgb)
679
+ │ XYZ D65 intermediate for Lab/LCH
680
+ │ Oklab/Oklch — Björn Ottosson matrices
681
+ │ Display P3 — ICC sRGB→P3 and P3→sRGB matrices
682
+
683
+ ├── Manipulation
684
+ │ lighten / darken / saturate / desaturate — via HSL
685
+ │ invertColor — RGB invert
686
+ │ grayscale — BT.709 perceptual weights
687
+ │ rotateHue — via HSL hue shift
688
+ │ adjustHexBrightness — linear channel blend toward 0/255
689
+ │ setAlpha / getAlpha — rgba string manipulation
690
+
691
+ ├── mixColors (core of interpolation system)
692
+ │ 6 color space modes: rgb | hsl | lab | lch | oklab | oklch
693
+ │ 4 hue interpolation modes: shorter | longer | increasing | decreasing
694
+ │ 4 output formats: hex | rgb | rgba | hsl
695
+ │ Used internally by: interpolateColors, createColorScale,
696
+ │ midpointColor, tints, shades, tones,
697
+ │ generateGradientColors*, generateTints*, generateShades*
698
+
699
+ ├── Color Harmonies
700
+ │ All implemented as hue rotations on normalized hex
701
+
702
+ ├── Palette Generation
703
+ │ colorShades — HSL lightness sweep
704
+ │ monochromatic — HSL saturation sweep
705
+ │ tints / shades / tones — Oklab interpolation via mixColors
706
+
707
+ ├── Accessibility
708
+ │ relativeLuminance — WCAG linearization formula
709
+ │ contrastRatio — (L1+0.05)/(L2+0.05)
710
+ │ wcagLevel — ratio thresholds 3 / 4.5 / 7
711
+ │ isReadableOnBackground — handles 3 background types;
712
+ │ semi-transparent: alpha-composites onto underlay before ratio check
713
+ │ gradient: evaluates each stop, uses minimum ratio
714
+ │ bestContrastPalette — score = avg×0.4 + min×0.6
715
+
716
+ ├── Color Blindness
717
+ │ Vienot 1999 matrices on linear RGB
718
+ │ 3 types: protanopia / deuteranopia / tritanopia
719
+
720
+ ├── Cache (Section 5.1)
721
+ │ Simple Map<string, NormalizeResult>
722
+ │ normalizeColorCached — Map lookup before calling normalizeColor
723
+ │ getCacheStats / clearColorCache / enableCache / disableCache
724
+
725
+ ├── Generator functions (Section 5.2)
726
+ │ ES2015 generator syntax (*) — yield one color at a time
727
+ │ generateGradientColors* → mixColors inside the generator
728
+ │ generateTints* / generateShades* → Oklab mix toward white/black
729
+
730
+ └── CLI (dist/cli/bin/cli.js → cvt)
731
+ Commands: info | convert | contrast | shades | harmonies | nearest
732
+ Accepts any color format normalizeColor understands
733
+ ```
345
734
 
346
- GitHub: [macrulezru](https://github.com/macrulezru)
735
+ ---
736
+
737
+ ## Bundle size & dependencies
738
+
739
+ | | |
740
+ |---|---|
741
+ | **Runtime dependencies** | None |
742
+ | **Peer dependencies** | None |
743
+ | **Dev dependencies** | TypeScript, Vitest |
744
+ | **ESM entry** | `dist/esm/index.js` (tree-shakeable) |
745
+ | **CJS entry** | `dist/cjs/index.js` |
746
+ | **CLI entry** | `dist/cli/bin/cli.js` → `cvt` |
747
+
748
+ The package ships both ESM (`type: module`) and CommonJS modules. Every function is a named export — unused functions are eliminated by bundlers that support tree-shaking.
347
749
 
348
- Website: [macrulez.ru](https://macrulez.ru/)
750
+ ---
349
751
 
350
752
  ## License
351
753
 
352
754
  MIT
755
+
756
+ ---
757
+
758
+ ## Author
759
+
760
+ Danil Lisin Vladimirovich aka Macrulez
761
+
762
+ GitHub: [macrulezru](https://github.com/macrulezru) · Website: [macrulez.ru/en](https://macrulez.ru/en)
763
+
764
+ Bugs and questions — [issues](https://github.com/macrulezru/color-value-tools/issues)
765
+
766
+ ---
767
+
768
+ ## 💖 Support the project
769
+
770
+ Open source takes time and effort. If my work saves you time or brings value, consider supporting further development.
771
+
772
+ <a href="https://donate.cryptocloud.plus/M6O34NIN" target="_blank">
773
+ <img src="https://img.shields.io/badge/Donate-CryptoCloud-8A2BE2?style=for-the-badge&logo=cryptocurrency&logoColor=white" alt="Donate via CryptoCloud">
774
+ </a>
775
+
776
+ Thank you for being part of this journey. ❤️
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "color-value-tools",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "A comprehensive utility library for parsing, converting, manipulating, and analyzing color values across all major color models — hex, RGB, HSL, HSV, HWB, Lab, LCH, OKLAB, OKLCH, CMYK — plus CSS variables and named colors.",
5
5
  "repository": {
6
6
  "type": "git",