color-value-tools 1.1.0 → 1.1.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.
package/dist/esm/index.js CHANGED
@@ -38,7 +38,13 @@ export function isCssVariable(value) {
38
38
  }
39
39
  export function isHexColor(value) {
40
40
  const trimmed = value.trim();
41
- return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(trimmed);
41
+ return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6})$/.test(trimmed);
42
+ }
43
+ export function isOklchColor(value) {
44
+ return /^\s*oklcha?\s*\(/i.test(value.trim());
45
+ }
46
+ export function isColorFunction(value) {
47
+ return /^\s*color\s*\(\s*(display-p3|srgb|srgb-linear)\s/i.test(value.trim());
42
48
  }
43
49
  export function isRgbColor(value) {
44
50
  const trimmed = value.trim().toLowerCase();
@@ -49,7 +55,8 @@ export function isHslColor(value) {
49
55
  return trimmed.startsWith('hsl(') || trimmed.startsWith('hsla(');
50
56
  }
51
57
  export function getColorType(value) {
52
- const trimmed = value.trim().toLowerCase();
58
+ const trimmed = value.trim();
59
+ const lower = trimmed.toLowerCase();
53
60
  if (isCssVariable(trimmed))
54
61
  return 'css-var';
55
62
  if (isHexColor(trimmed))
@@ -58,13 +65,116 @@ export function getColorType(value) {
58
65
  return 'rgb';
59
66
  if (isHslColor(trimmed))
60
67
  return 'hsl';
61
- if (trimmed in NAMED_COLORS)
68
+ if (isOklchColor(trimmed))
69
+ return 'oklch';
70
+ if (isColorFunction(trimmed))
71
+ return 'color';
72
+ if (lower in NAMED_COLORS)
62
73
  return 'named';
63
74
  return 'unknown';
64
75
  }
65
76
  export function extractCssVariableName(value) {
66
- const match = value.match(/var\((--[^)]+)\)/);
67
- return (match === null || match === void 0 ? void 0 : match[1]) || value;
77
+ var _a;
78
+ const match = value.match(/var\(\s*(--[^,)]+)/);
79
+ return ((_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.trim()) || value;
80
+ }
81
+ export function parseCssVar(value) {
82
+ var _a;
83
+ const m = value.trim().match(/^var\(\s*(--[^,)]+)(?:,\s*([\s\S]+?))?\s*\)$/);
84
+ if (!m)
85
+ return null;
86
+ return { variableName: m[1].trim(), fallback: (_a = m[2]) === null || _a === void 0 ? void 0 : _a.trim() };
87
+ }
88
+ // ─── Section 1.1 — OKLCH / color() CSS string parsing ────────────────────────
89
+ export function parseOklchString(str) {
90
+ const m = str.trim().match(/oklcha?\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\s*\)/i);
91
+ if (!m)
92
+ return null;
93
+ let L = parseFloat(m[1]);
94
+ if (m[1].endsWith('%'))
95
+ L = L / 100;
96
+ const C = parseFloat(m[2]);
97
+ const H = parseFloat(m[3]);
98
+ const alpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
99
+ return { L, C, H, alpha };
100
+ }
101
+ export function parseColorFn(str) {
102
+ const m = str.trim().match(/color\(\s*([\w-]+)\s+([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+%?)(?:\s*\/\s*([\d.]+))?\s*\)/i);
103
+ if (!m)
104
+ return null;
105
+ const space = m[1].toLowerCase();
106
+ const parseVal = (v) => v.endsWith('%') ? parseFloat(v) / 100 : parseFloat(v);
107
+ const r = parseVal(m[2]);
108
+ const g = parseVal(m[3]);
109
+ const b = parseVal(m[4]);
110
+ const alpha = m[5] !== undefined ? parseFloat(m[5]) : 1;
111
+ return { space, r, g, b, alpha };
112
+ }
113
+ // ─── Section 1.2 — Display P3 ────────────────────────────────────────────────
114
+ export function rgbToDisplayP3(rgb) {
115
+ // Remove sRGB gamma (linearize)
116
+ const rl = srgbChanToLinear(rgb.r / 255);
117
+ const gl = srgbChanToLinear(rgb.g / 255);
118
+ const bl = srgbChanToLinear(rgb.b / 255);
119
+ // sRGB-linear → P3-linear matrix
120
+ const pr = 0.8226 * rl + 0.1774 * gl + 0.0000 * bl;
121
+ const pg = 0.0332 * rl + 0.9669 * gl + 0.0000 * bl;
122
+ const pb = 0.0171 * rl + 0.0724 * gl + 0.9103 * bl;
123
+ // Apply P3 gamma (same as sRGB)
124
+ return {
125
+ r: linearChanToSrgb(pr),
126
+ g: linearChanToSrgb(pg),
127
+ b: linearChanToSrgb(pb),
128
+ };
129
+ }
130
+ export function displayP3ToRgb(p3) {
131
+ // Remove P3 gamma
132
+ const rl = srgbChanToLinear(p3.r);
133
+ const gl = srgbChanToLinear(p3.g);
134
+ const bl = srgbChanToLinear(p3.b);
135
+ // P3-linear → sRGB-linear inverse matrix
136
+ const sr = 1.2247 * rl + (-0.2247) * gl + 0.0000 * bl;
137
+ const sg = (-0.0421) * rl + 1.0432 * gl + 0.0000 * bl;
138
+ const sb = (-0.0197) * rl + (-0.0786) * gl + 1.0983 * bl;
139
+ // Apply sRGB gamma
140
+ return {
141
+ r: Math.round(Math.max(0, Math.min(255, linearChanToSrgb(sr)))),
142
+ g: Math.round(Math.max(0, Math.min(255, linearChanToSrgb(sg)))),
143
+ b: Math.round(Math.max(0, Math.min(255, linearChanToSrgb(sb)))),
144
+ };
145
+ }
146
+ // Helpers for Display P3 (operate on 0-1 values)
147
+ function srgbChanToLinear(v) {
148
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
149
+ }
150
+ function linearChanToSrgb(v) {
151
+ const t = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
152
+ return Math.max(0, Math.min(1, t));
153
+ }
154
+ export function toDisplayP3Hex(color) {
155
+ const n = normalizeColorRaw(color);
156
+ const p3 = rgbToDisplayP3({ r: n.r, g: n.g, b: n.b });
157
+ const back = displayP3ToRgb(p3);
158
+ return rgbToHex(back);
159
+ }
160
+ // Internal raw normalizer (no cache, no circular dep issues)
161
+ function normalizeColorRaw(color) {
162
+ var _a, _b, _c, _d;
163
+ const n = normalizeColor(color);
164
+ return { 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, a: (_d = n.a) !== null && _d !== void 0 ? _d : 1 };
165
+ }
166
+ // ─── Section 1.3 — Short RGBA hex #RGBA ──────────────────────────────────────
167
+ export function shortHexToRgba(hex) {
168
+ let h = hex.trim();
169
+ if (!h.startsWith('#'))
170
+ h = `#${h}`;
171
+ if (!/^#[0-9a-f]{4}$/i.test(h))
172
+ return null;
173
+ const r = parseInt(h[1] + h[1], 16);
174
+ const g = parseInt(h[2] + h[2], 16);
175
+ const b = parseInt(h[3] + h[3], 16);
176
+ const a = parseInt(h[4] + h[4], 16) / 255;
177
+ return { r, g, b, a };
68
178
  }
69
179
  export function normalizeHex(hex) {
70
180
  let cleanHex = hex.trim();
@@ -494,6 +604,52 @@ export function normalizeColor(input) {
494
604
  }
495
605
  if (str === 'transparent')
496
606
  return { type: 'named', hex: '#000000', r: 0, g: 0, b: 0, a: 0 };
607
+ // 4-digit #RGBA hex
608
+ if (/^#[0-9a-f]{4}$/i.test(str)) {
609
+ const rgba = shortHexToRgba(str);
610
+ if (rgba) {
611
+ const { r, g, b, a } = rgba;
612
+ const hex = rgbToHex({ r, g, b });
613
+ const [h, s, l] = rgbToHsl({ r, g, b });
614
+ const [, , vv] = rgbToHsv({ r, g, b });
615
+ return { type: 'hex', hex, r, g, b, a, h, s, l, v: vv };
616
+ }
617
+ }
618
+ // oklch / oklcha
619
+ if (isOklchColor(str)) {
620
+ const parsed = parseOklchString(str);
621
+ if (parsed) {
622
+ const { r, g, b } = oklchToRgb({ L: parsed.L, C: parsed.C, H: parsed.H });
623
+ const hex = rgbToHex({ r, g, b });
624
+ const [h, s, l] = rgbToHsl({ r, g, b });
625
+ const [, , vv] = rgbToHsv({ r, g, b });
626
+ return { type: 'oklch', hex, r, g, b, a: parsed.alpha, h, s, l, v: vv };
627
+ }
628
+ }
629
+ // color(display-p3 ...) / color(srgb ...)
630
+ if (isColorFunction(str)) {
631
+ const parsed = parseColorFn(str);
632
+ if (parsed) {
633
+ const { r: p3r, g: p3g, b: p3b, alpha } = parsed;
634
+ // Convert from p3/srgb (0-1) to sRGB 0-255
635
+ let r, g, b;
636
+ if (parsed.space === 'display-p3') {
637
+ const srgb = displayP3ToRgb({ r: p3r, g: p3g, b: p3b });
638
+ r = srgb.r;
639
+ g = srgb.g;
640
+ b = srgb.b;
641
+ }
642
+ else {
643
+ r = Math.round(Math.max(0, Math.min(255, p3r * 255)));
644
+ g = Math.round(Math.max(0, Math.min(255, p3g * 255)));
645
+ b = Math.round(Math.max(0, Math.min(255, p3b * 255)));
646
+ }
647
+ const hex = rgbToHex({ r, g, b });
648
+ const [h, s, l] = rgbToHsl({ r, g, b });
649
+ const [, , vv] = rgbToHsv({ r, g, b });
650
+ return { type: 'color', hex, r, g, b, a: alpha, h, s, l, v: vv };
651
+ }
652
+ }
497
653
  return { type: 'unknown' };
498
654
  }
499
655
  export function complement(color) {
@@ -544,47 +700,109 @@ export function monochromatic(color, steps = 5) {
544
700
  }
545
701
  return result;
546
702
  }
703
+ function _lerpHue(h1, h2, t, mode = 'shorter') {
704
+ let d = h2 - h1;
705
+ if (mode === 'shorter') {
706
+ if (d > 180)
707
+ d -= 360;
708
+ else if (d < -180)
709
+ d += 360;
710
+ }
711
+ else if (mode === 'longer') {
712
+ if (d > 0 && d < 180)
713
+ d -= 360;
714
+ else if (d < 0 && d > -180)
715
+ d += 360;
716
+ }
717
+ else if (mode === 'increasing') {
718
+ if (d < 0)
719
+ d += 360;
720
+ }
721
+ else if (mode === 'decreasing') {
722
+ if (d > 0)
723
+ d -= 360;
724
+ }
725
+ return h1 + d * t;
726
+ }
727
+ function _formatMixResult(r, g, b, a, out) {
728
+ if (out === 'hex')
729
+ return rgbToHex({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
730
+ if (out === 'rgba')
731
+ return rgbToRgbaString({ r: Math.round(r), g: Math.round(g), b: Math.round(b) }, a);
732
+ if (out === 'rgb')
733
+ return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
734
+ if (out === 'hsl') {
735
+ const [hh, ss, ll] = rgbToHsl({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
736
+ return `hsl(${hh}, ${ss}%, ${ll}%)`;
737
+ }
738
+ return rgbToHex({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
739
+ }
547
740
  export function mixColors(c1, c2, t, opts) {
548
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
741
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35;
549
742
  const o1 = normalizeColor(c1);
550
743
  const o2 = normalizeColor(c2);
551
744
  t = Math.max(0, Math.min(1, t));
552
745
  const mode = (opts === null || opts === void 0 ? void 0 : opts.mode) || 'rgb';
553
746
  const out = (opts === null || opts === void 0 ? void 0 : opts.format) || 'hex';
747
+ const hueMode = (_a = opts === null || opts === void 0 ? void 0 : opts.hueInterpolation) !== null && _a !== void 0 ? _a : 'shorter';
554
748
  let r = 0, g = 0, b = 0, a = 1;
555
749
  if (mode === 'hsl') {
556
- const h1 = (_a = o1.h) !== null && _a !== void 0 ? _a : rgbToHsl({ r: (_b = o1.r) !== null && _b !== void 0 ? _b : 0, g: (_c = o1.g) !== null && _c !== void 0 ? _c : 0, b: (_d = o1.b) !== null && _d !== void 0 ? _d : 0 })[0];
557
- const s1 = (_e = o1.s) !== null && _e !== void 0 ? _e : 0;
558
- const l1 = (_f = o1.l) !== null && _f !== void 0 ? _f : 0;
559
- const h2 = (_g = o2.h) !== null && _g !== void 0 ? _g : rgbToHsl({ r: (_h = o2.r) !== null && _h !== void 0 ? _h : 0, g: (_j = o2.g) !== null && _j !== void 0 ? _j : 0, b: (_k = o2.b) !== null && _k !== void 0 ? _k : 0 })[0];
560
- const s2 = (_l = o2.s) !== null && _l !== void 0 ? _l : 0;
561
- const l2 = (_m = o2.l) !== null && _m !== void 0 ? _m : 0;
562
- const ih = h1 + (((h2 - h1 + 540) % 360) - 180) * t;
563
- const is = s1 + (s2 - s1) * t;
564
- const il = l1 + (l2 - l1) * t;
565
- const rgb = hslToRgb(ih, is, il);
750
+ const h1 = (_b = o1.h) !== null && _b !== void 0 ? _b : rgbToHsl({ r: (_c = o1.r) !== null && _c !== void 0 ? _c : 0, g: (_d = o1.g) !== null && _d !== void 0 ? _d : 0, b: (_e = o1.b) !== null && _e !== void 0 ? _e : 0 })[0];
751
+ const s1 = (_f = o1.s) !== null && _f !== void 0 ? _f : 0;
752
+ const l1 = (_g = o1.l) !== null && _g !== void 0 ? _g : 0;
753
+ const h2 = (_h = o2.h) !== null && _h !== void 0 ? _h : rgbToHsl({ r: (_j = o2.r) !== null && _j !== void 0 ? _j : 0, g: (_k = o2.g) !== null && _k !== void 0 ? _k : 0, b: (_l = o2.b) !== null && _l !== void 0 ? _l : 0 })[0];
754
+ const s2 = (_m = o2.s) !== null && _m !== void 0 ? _m : 0;
755
+ const l2 = (_o = o2.l) !== null && _o !== void 0 ? _o : 0;
756
+ const ih = _lerpHue(h1, h2, t, hueMode);
757
+ const rgb = hslToRgb(ih, s1 + (s2 - s1) * t, l1 + (l2 - l1) * t);
566
758
  r = rgb.r;
567
759
  g = rgb.g;
568
760
  b = rgb.b;
569
- a = ((_o = o1.a) !== null && _o !== void 0 ? _o : 1) + (((_p = o2.a) !== null && _p !== void 0 ? _p : 1) - ((_q = o1.a) !== null && _q !== void 0 ? _q : 1)) * t;
761
+ a = ((_p = o1.a) !== null && _p !== void 0 ? _p : 1) + (((_q = o2.a) !== null && _q !== void 0 ? _q : 1) - ((_r = o1.a) !== null && _r !== void 0 ? _r : 1)) * t;
570
762
  }
571
- else {
572
- r = (((_r = o1.r) !== null && _r !== void 0 ? _r : 0) * (1 - t)) + (((_s = o2.r) !== null && _s !== void 0 ? _s : 0) * t);
573
- g = (((_t = o1.g) !== null && _t !== void 0 ? _t : 0) * (1 - t)) + (((_u = o2.g) !== null && _u !== void 0 ? _u : 0) * t);
574
- b = (((_v = o1.b) !== null && _v !== void 0 ? _v : 0) * (1 - t)) + (((_w = o2.b) !== null && _w !== void 0 ? _w : 0) * t);
575
- a = ((_x = o1.a) !== null && _x !== void 0 ? _x : 1) * (1 - t) + ((_y = o2.a) !== null && _y !== void 0 ? _y : 1) * t;
763
+ else if (mode === 'lab') {
764
+ const lab1 = rgbToLab({ r: (_s = o1.r) !== null && _s !== void 0 ? _s : 0, g: (_t = o1.g) !== null && _t !== void 0 ? _t : 0, b: (_u = o1.b) !== null && _u !== void 0 ? _u : 0 });
765
+ const lab2 = rgbToLab({ r: (_v = o2.r) !== null && _v !== void 0 ? _v : 0, g: (_w = o2.g) !== null && _w !== void 0 ? _w : 0, b: (_x = o2.b) !== null && _x !== void 0 ? _x : 0 });
766
+ const mixed = labToRgb({ L: lab1.L + (lab2.L - lab1.L) * t, a: lab1.a + (lab2.a - lab1.a) * t, b: lab1.b + (lab2.b - lab1.b) * t });
767
+ r = mixed.r;
768
+ g = mixed.g;
769
+ b = mixed.b;
770
+ a = ((_y = o1.a) !== null && _y !== void 0 ? _y : 1) + (((_z = o2.a) !== null && _z !== void 0 ? _z : 1) - ((_0 = o1.a) !== null && _0 !== void 0 ? _0 : 1)) * t;
576
771
  }
577
- if (out === 'hex')
578
- return rgbToHex({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
579
- if (out === 'rgba')
580
- return rgbToRgbaString({ r: Math.round(r), g: Math.round(g), b: Math.round(b) }, a);
581
- if (out === 'rgb')
582
- return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
583
- if (out === 'hsl') {
584
- const [hh, ss, ll] = rgbToHsl({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
585
- return `hsl(${hh}, ${ss}%, ${ll}%)`;
772
+ else if (mode === 'lch') {
773
+ const lch1 = rgbToLch({ r: (_1 = o1.r) !== null && _1 !== void 0 ? _1 : 0, g: (_2 = o1.g) !== null && _2 !== void 0 ? _2 : 0, b: (_3 = o1.b) !== null && _3 !== void 0 ? _3 : 0 });
774
+ const lch2 = rgbToLch({ r: (_4 = o2.r) !== null && _4 !== void 0 ? _4 : 0, g: (_5 = o2.g) !== null && _5 !== void 0 ? _5 : 0, b: (_6 = o2.b) !== null && _6 !== void 0 ? _6 : 0 });
775
+ const mixed = lchToRgb({ L: lch1.L + (lch2.L - lch1.L) * t, C: lch1.C + (lch2.C - lch1.C) * t, H: _lerpHue(lch1.H, lch2.H, t, hueMode) });
776
+ r = mixed.r;
777
+ g = mixed.g;
778
+ b = mixed.b;
779
+ a = ((_7 = o1.a) !== null && _7 !== void 0 ? _7 : 1) + (((_8 = o2.a) !== null && _8 !== void 0 ? _8 : 1) - ((_9 = o1.a) !== null && _9 !== void 0 ? _9 : 1)) * t;
586
780
  }
587
- return rgbToHex({ r: Math.round(r), g: Math.round(g), b: Math.round(b) });
781
+ else if (mode === 'oklab') {
782
+ const ok1 = rgbToOklab({ r: (_10 = o1.r) !== null && _10 !== void 0 ? _10 : 0, g: (_11 = o1.g) !== null && _11 !== void 0 ? _11 : 0, b: (_12 = o1.b) !== null && _12 !== void 0 ? _12 : 0 });
783
+ const ok2 = rgbToOklab({ r: (_13 = o2.r) !== null && _13 !== void 0 ? _13 : 0, g: (_14 = o2.g) !== null && _14 !== void 0 ? _14 : 0, b: (_15 = o2.b) !== null && _15 !== void 0 ? _15 : 0 });
784
+ const mixed = oklabToRgb({ L: ok1.L + (ok2.L - ok1.L) * t, a: ok1.a + (ok2.a - ok1.a) * t, b: ok1.b + (ok2.b - ok1.b) * t });
785
+ r = mixed.r;
786
+ g = mixed.g;
787
+ b = mixed.b;
788
+ a = ((_16 = o1.a) !== null && _16 !== void 0 ? _16 : 1) + (((_17 = o2.a) !== null && _17 !== void 0 ? _17 : 1) - ((_18 = o1.a) !== null && _18 !== void 0 ? _18 : 1)) * t;
789
+ }
790
+ else if (mode === 'oklch') {
791
+ const ok1 = rgbToOklch({ r: (_19 = o1.r) !== null && _19 !== void 0 ? _19 : 0, g: (_20 = o1.g) !== null && _20 !== void 0 ? _20 : 0, b: (_21 = o1.b) !== null && _21 !== void 0 ? _21 : 0 });
792
+ const ok2 = rgbToOklch({ r: (_22 = o2.r) !== null && _22 !== void 0 ? _22 : 0, g: (_23 = o2.g) !== null && _23 !== void 0 ? _23 : 0, b: (_24 = o2.b) !== null && _24 !== void 0 ? _24 : 0 });
793
+ const mixed = oklchToRgb({ L: ok1.L + (ok2.L - ok1.L) * t, C: ok1.C + (ok2.C - ok1.C) * t, H: _lerpHue(ok1.H, ok2.H, t, hueMode) });
794
+ r = mixed.r;
795
+ g = mixed.g;
796
+ b = mixed.b;
797
+ a = ((_25 = o1.a) !== null && _25 !== void 0 ? _25 : 1) + (((_26 = o2.a) !== null && _26 !== void 0 ? _26 : 1) - ((_27 = o1.a) !== null && _27 !== void 0 ? _27 : 1)) * t;
798
+ }
799
+ else {
800
+ r = ((_28 = o1.r) !== null && _28 !== void 0 ? _28 : 0) * (1 - t) + ((_29 = o2.r) !== null && _29 !== void 0 ? _29 : 0) * t;
801
+ g = ((_30 = o1.g) !== null && _30 !== void 0 ? _30 : 0) * (1 - t) + ((_31 = o2.g) !== null && _31 !== void 0 ? _31 : 0) * t;
802
+ b = ((_32 = o1.b) !== null && _32 !== void 0 ? _32 : 0) * (1 - t) + ((_33 = o2.b) !== null && _33 !== void 0 ? _33 : 0) * t;
803
+ a = ((_34 = o1.a) !== null && _34 !== void 0 ? _34 : 1) * (1 - t) + ((_35 = o2.a) !== null && _35 !== void 0 ? _35 : 1) * t;
804
+ }
805
+ return _formatMixResult(r, g, b, a, out);
588
806
  }
589
807
  export function relativeLuminance(color) {
590
808
  var _a, _b, _c;
@@ -905,3 +1123,188 @@ export function toNearestNamedColor(color) {
905
1123
  }
906
1124
  return bestName;
907
1125
  }
1126
+ // ─── Formatting ───────────────────────────────────────────────────────────────
1127
+ export function toOklchString(color, alpha) {
1128
+ var _a, _b, _c;
1129
+ const n = normalizeColor(color);
1130
+ const { L, C, H } = rgbToOklch({ 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 });
1131
+ const Lr = +L.toFixed(4), Cr = +C.toFixed(4), Hr = +H.toFixed(2);
1132
+ if (alpha !== undefined)
1133
+ return `oklch(${Lr} ${Cr} ${Hr} / ${+alpha.toFixed(3)})`;
1134
+ return `oklch(${Lr} ${Cr} ${Hr})`;
1135
+ }
1136
+ export function toColorP3String(color, alpha) {
1137
+ var _a, _b, _c;
1138
+ const n = normalizeColor(color);
1139
+ const p3 = rgbToDisplayP3({ 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 });
1140
+ const r = +p3.r.toFixed(4), g = +p3.g.toFixed(4), b = +p3.b.toFixed(4);
1141
+ if (alpha !== undefined)
1142
+ return `color(display-p3 ${r} ${g} ${b} / ${+alpha.toFixed(3)})`;
1143
+ return `color(display-p3 ${r} ${g} ${b})`;
1144
+ }
1145
+ // ─── Section 2: Interpolation ─────────────────────────────────────────────────
1146
+ export function interpolateColors(color1, color2, steps, options) {
1147
+ if (steps < 2)
1148
+ return steps === 1 ? [mixColors(color1, color2, 0.5, options)] : [];
1149
+ const result = [];
1150
+ for (let i = 0; i < steps; i++) {
1151
+ result.push(mixColors(color1, color2, i / (steps - 1), options));
1152
+ }
1153
+ return result;
1154
+ }
1155
+ export function createColorScale(anchors, steps, options) {
1156
+ const normalized = anchors.map((a, i, arr) => {
1157
+ var _a;
1158
+ return ({
1159
+ color: typeof a === 'string' ? a : a.color,
1160
+ position: typeof a === 'string' ? i / Math.max(arr.length - 1, 1) : ((_a = a.position) !== null && _a !== void 0 ? _a : i / Math.max(arr.length - 1, 1)),
1161
+ });
1162
+ });
1163
+ normalized.sort((a, b) => a.position - b.position);
1164
+ const result = [];
1165
+ for (let i = 0; i < steps; i++) {
1166
+ const t = steps === 1 ? 0 : i / (steps - 1);
1167
+ let lo = normalized[0], hi = normalized[normalized.length - 1];
1168
+ for (let j = 0; j < normalized.length - 1; j++) {
1169
+ if (t >= normalized[j].position && t <= normalized[j + 1].position) {
1170
+ lo = normalized[j];
1171
+ hi = normalized[j + 1];
1172
+ break;
1173
+ }
1174
+ }
1175
+ const span = hi.position - lo.position;
1176
+ const localT = span === 0 ? 0 : (t - lo.position) / span;
1177
+ result.push(mixColors(lo.color, hi.color, localT, options));
1178
+ }
1179
+ return result;
1180
+ }
1181
+ export function midpointColor(color1, color2, options) {
1182
+ var _a;
1183
+ return mixColors(color1, color2, 0.5, { mode: (_a = options === null || options === void 0 ? void 0 : options.space) !== null && _a !== void 0 ? _a : 'oklab' });
1184
+ }
1185
+ // ─── Section 3: Tints / Shades / Tones ───────────────────────────────────────
1186
+ export function tints(color, steps = 5) {
1187
+ return interpolateColors(color, '#ffffff', steps, { space: 'oklab' });
1188
+ }
1189
+ export function shades(color, steps = 5) {
1190
+ return interpolateColors(color, '#000000', steps, { space: 'oklab' });
1191
+ }
1192
+ export function tones(color, steps = 5, gray = '#808080') {
1193
+ return interpolateColors(color, gray, steps, { space: 'oklab' });
1194
+ }
1195
+ // ─── Section 4: Color blindness simulation ────────────────────────────────────
1196
+ function _simulateCB(color, matrix) {
1197
+ var _a, _b, _c;
1198
+ const n = normalizeColor(color);
1199
+ // linearize
1200
+ const lin = (v) => { const c = v / 255; return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); };
1201
+ const enc = (v) => { const c = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055; return Math.round(Math.max(0, Math.min(1, c)) * 255); };
1202
+ const r = lin((_a = n.r) !== null && _a !== void 0 ? _a : 0), g = lin((_b = n.g) !== null && _b !== void 0 ? _b : 0), b = lin((_c = n.b) !== null && _c !== void 0 ? _c : 0);
1203
+ const nr = enc(matrix[0][0] * r + matrix[0][1] * g + matrix[0][2] * b);
1204
+ const ng = enc(matrix[1][0] * r + matrix[1][1] * g + matrix[1][2] * b);
1205
+ const nb = enc(matrix[2][0] * r + matrix[2][1] * g + matrix[2][2] * b);
1206
+ return rgbToHex({ r: nr, g: ng, b: nb });
1207
+ }
1208
+ const _PROTANOPIA_M = [[0.56667, 0.43333, 0.00000], [0.55833, 0.44167, 0.00000], [0.00000, 0.24167, 0.75833]];
1209
+ const _DEUTERANOPIA_M = [[0.62500, 0.37500, 0.00000], [0.70000, 0.30000, 0.00000], [0.00000, 0.30000, 0.70000]];
1210
+ const _TRITANOPIA_M = [[0.95000, 0.05000, 0.00000], [0.00000, 0.43333, 0.56667], [0.00000, 0.47500, 0.52500]];
1211
+ export function simulateProtanopia(color) { return _simulateCB(color, _PROTANOPIA_M); }
1212
+ export function simulateDeuteranopia(color) { return _simulateCB(color, _DEUTERANOPIA_M); }
1213
+ export function simulateTritanopia(color) { return _simulateCB(color, _TRITANOPIA_M); }
1214
+ export function simulateColorBlindness(color, type) {
1215
+ if (type === 'protanopia')
1216
+ return simulateProtanopia(color);
1217
+ if (type === 'deuteranopia')
1218
+ return simulateDeuteranopia(color);
1219
+ return simulateTritanopia(color);
1220
+ }
1221
+ export function isReadableOnBackground(textColor, background, options) {
1222
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1223
+ const level = (_a = options === null || options === void 0 ? void 0 : options.level) !== null && _a !== void 0 ? _a : 'AA';
1224
+ const large = (_b = options === null || options === void 0 ? void 0 : options.largeText) !== null && _b !== void 0 ? _b : false;
1225
+ const minRequired = level === 'AAA' ? (large ? 4.5 : 7) : (large ? 3 : 4.5);
1226
+ let minRatio;
1227
+ if (typeof background === 'string') {
1228
+ minRatio = contrastRatio(textColor, background);
1229
+ }
1230
+ else if (background.type === 'semi-transparent') {
1231
+ const underlay = (_c = background.underlay) !== null && _c !== void 0 ? _c : '#ffffff';
1232
+ const fg = normalizeColor(background.color);
1233
+ const bg = normalizeColor(underlay);
1234
+ const alpha = (_d = fg.a) !== null && _d !== void 0 ? _d : 1;
1235
+ const cr = Math.round(((_e = fg.r) !== null && _e !== void 0 ? _e : 0) * alpha + ((_f = bg.r) !== null && _f !== void 0 ? _f : 255) * (1 - alpha));
1236
+ const cg = Math.round(((_g = fg.g) !== null && _g !== void 0 ? _g : 0) * alpha + ((_h = bg.g) !== null && _h !== void 0 ? _h : 255) * (1 - alpha));
1237
+ const cb = Math.round(((_j = fg.b) !== null && _j !== void 0 ? _j : 0) * alpha + ((_k = bg.b) !== null && _k !== void 0 ? _k : 255) * (1 - alpha));
1238
+ minRatio = contrastRatio(textColor, rgbToHex({ r: cr, g: cg, b: cb }));
1239
+ }
1240
+ else {
1241
+ const ratios = background.stops.map(stop => contrastRatio(textColor, stop));
1242
+ minRatio = Math.min(...ratios);
1243
+ }
1244
+ const wLevel = wcagLevel(textColor, typeof background === 'string' ? background : '#ffffff');
1245
+ return { readable: minRatio >= minRequired, minContrastRatio: +minRatio.toFixed(2), wcagLevel: wLevel };
1246
+ }
1247
+ export function bestContrastPalette(background, palettes, options) {
1248
+ let bestIdx = 0;
1249
+ let bestScore = -1;
1250
+ let bestMin = 0, bestAvg = 0;
1251
+ palettes.forEach((palette, idx) => {
1252
+ var _a;
1253
+ const ratios = palette.map(c => contrastRatio(background, c));
1254
+ const weights = (_a = options === null || options === void 0 ? void 0 : options.weights) !== null && _a !== void 0 ? _a : ratios.map(() => 1);
1255
+ const totalW = weights.reduce((s, w) => s + w, 0);
1256
+ const avg = ratios.reduce((s, r, i) => { var _a; return s + r * ((_a = weights[i]) !== null && _a !== void 0 ? _a : 1); }, 0) / totalW;
1257
+ const min = Math.min(...ratios);
1258
+ // Score: weighted avg with heavy penalty on minimum
1259
+ const score = avg * 0.4 + min * 0.6;
1260
+ if (score > bestScore) {
1261
+ bestScore = score;
1262
+ bestIdx = idx;
1263
+ bestMin = min;
1264
+ bestAvg = avg;
1265
+ }
1266
+ });
1267
+ return {
1268
+ paletteIndex: bestIdx,
1269
+ palette: palettes[bestIdx],
1270
+ minContrastRatio: +bestMin.toFixed(2),
1271
+ avgContrastRatio: +bestAvg.toFixed(2),
1272
+ };
1273
+ }
1274
+ // ─── Section 5.1: Caching ────────────────────────────────────────────────────
1275
+ let _cacheEnabled = true;
1276
+ const _normalizeCache = new Map();
1277
+ let _cacheHits = 0;
1278
+ export function clearColorCache() { _normalizeCache.clear(); _cacheHits = 0; }
1279
+ export function getCacheStats() { return { size: _normalizeCache.size, hits: _cacheHits }; }
1280
+ export function enableCache() { _cacheEnabled = true; }
1281
+ export function disableCache() { _cacheEnabled = false; }
1282
+ // Cached normalizer wrapper — use this in performance-sensitive contexts
1283
+ export function normalizeColorCached(input) {
1284
+ if (!_cacheEnabled)
1285
+ return normalizeColor(input);
1286
+ const cached = _normalizeCache.get(input);
1287
+ if (cached) {
1288
+ _cacheHits++;
1289
+ return cached;
1290
+ }
1291
+ const result = normalizeColor(input);
1292
+ _normalizeCache.set(input, result);
1293
+ return result;
1294
+ }
1295
+ // ─── Section 5.2: Generator functions ────────────────────────────────────────
1296
+ export function* generateGradientColors(start, end, steps, options) {
1297
+ for (let i = 0; i < steps; i++) {
1298
+ yield mixColors(start, end, steps === 1 ? 0 : i / (steps - 1), options);
1299
+ }
1300
+ }
1301
+ export function* generateTints(color, steps, options) {
1302
+ for (let i = 0; i < steps; i++) {
1303
+ yield mixColors(color, '#ffffff', steps === 1 ? 0 : i / (steps - 1), { mode: 'oklab', ...options });
1304
+ }
1305
+ }
1306
+ export function* generateShades(color, steps, options) {
1307
+ for (let i = 0; i < steps; i++) {
1308
+ yield mixColors(color, '#000000', steps === 1 ? 0 : i / (steps - 1), { mode: 'oklab', ...options });
1309
+ }
1310
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "color-value-tools",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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",
@@ -20,11 +20,14 @@
20
20
  "types": "./dist/cjs/index.d.ts"
21
21
  }
22
22
  },
23
+ "bin": {
24
+ "cvt": "./dist/cli/bin/cli.js"
25
+ },
23
26
  "files": [
24
27
  "dist"
25
28
  ],
26
29
  "scripts": {
27
- "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({type:'module'}))\"",
30
+ "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'}))\"",
28
31
  "test": "vitest run"
29
32
  },
30
33
  "keywords": [
@@ -47,6 +50,7 @@
47
50
  "author": "",
48
51
  "license": "MIT",
49
52
  "devDependencies": {
53
+ "@types/node": "^25.5.0",
50
54
  "typescript": "^5.0.0",
51
55
  "vitest": "^2.0.0"
52
56
  }