saturon 0.2.2 → 0.2.3
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/Color.d.ts +20 -11
- package/dist/Color.js +127 -97
- package/dist/converters.d.ts +4 -4
- package/dist/converters.js +14 -20
- package/dist/index.umd.js +163 -149
- package/dist/index.umd.min.js +1 -1
- package/dist/math.d.ts +1 -0
- package/dist/math.js +11 -10
- package/dist/temp.d.ts +1 -0
- package/dist/temp.js +3 -0
- package/dist/types.d.ts +4 -4
- package/dist/utils.js +26 -30
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -165,11 +165,11 @@ var Saturon = (() => {
|
|
|
165
165
|
if (method === "none") {
|
|
166
166
|
clipped = coords;
|
|
167
167
|
} else if (method === "clip") {
|
|
168
|
-
clipped = coords.
|
|
168
|
+
clipped = coords.map((v, i) => {
|
|
169
169
|
const prop = componentProps[i];
|
|
170
170
|
if (!prop)
|
|
171
171
|
throw new Error(`Missing component properties for index ${i}.`);
|
|
172
|
-
if (prop.value === "
|
|
172
|
+
if (prop.value === "hue")
|
|
173
173
|
return (v % 360 + 360) % 360;
|
|
174
174
|
const [min, max] = Array.isArray(prop.value) ? prop.value : [0, 100];
|
|
175
175
|
return Math.min(max, Math.max(min, v));
|
|
@@ -181,7 +181,7 @@ var Saturon = (() => {
|
|
|
181
181
|
}
|
|
182
182
|
clipped = fn(coords, model);
|
|
183
183
|
}
|
|
184
|
-
return clipped.
|
|
184
|
+
return clipped.map((v, i) => {
|
|
185
185
|
let p;
|
|
186
186
|
if (typeof precision === "number" || precision === null)
|
|
187
187
|
p = precision;
|
|
@@ -204,10 +204,10 @@ var Saturon = (() => {
|
|
|
204
204
|
return percent / 100 * (max2 - min2) / 2;
|
|
205
205
|
return percent / 100 * (max2 - min2) + min2;
|
|
206
206
|
};
|
|
207
|
-
const
|
|
207
|
+
const parseHue = (token2) => {
|
|
208
208
|
const value2 = parseFloat(token2);
|
|
209
209
|
if (isNaN(value2))
|
|
210
|
-
throw new Error(`Invalid
|
|
210
|
+
throw new Error(`Invalid hue value: '${token2}'.`);
|
|
211
211
|
if (token2.slice(-3) === "deg")
|
|
212
212
|
return value2;
|
|
213
213
|
if (token2.slice(-3) === "rad")
|
|
@@ -400,21 +400,21 @@ var Saturon = (() => {
|
|
|
400
400
|
};
|
|
401
401
|
let inner = token2.slice(5, -1).trim();
|
|
402
402
|
if (inner === "infinity")
|
|
403
|
-
return
|
|
403
|
+
return Infinity;
|
|
404
404
|
if (inner === "-infinity")
|
|
405
|
-
return
|
|
405
|
+
return -Infinity;
|
|
406
406
|
if (inner === "NaN")
|
|
407
|
-
return
|
|
407
|
+
return NaN;
|
|
408
408
|
inner = inner.replace(/(\d+(\.\d+)?)%/g, (m) => {
|
|
409
409
|
if (relative === true)
|
|
410
|
-
throw new Error("<
|
|
410
|
+
throw new Error("<hue> and <percentage> values are converted to <number> in relative syntax.");
|
|
411
411
|
const r = parsePercent(m, _min, _max);
|
|
412
412
|
return r !== void 0 ? String(r) : "0";
|
|
413
413
|
});
|
|
414
414
|
inner = inner.replace(/(\d+(\.\d+)?)(deg|rad|grad|turn)/g, (_, num, __, unit) => {
|
|
415
415
|
if (relative === true)
|
|
416
|
-
throw new Error("<
|
|
417
|
-
return String(
|
|
416
|
+
throw new Error("<hue> and <percentage> values are converted to <number> in relative syntax.");
|
|
417
|
+
return String(parseHue(`${parseFloat(num)}${unit}`));
|
|
418
418
|
});
|
|
419
419
|
const caclEnv = {
|
|
420
420
|
...base,
|
|
@@ -454,27 +454,18 @@ var Saturon = (() => {
|
|
|
454
454
|
throw new Error(`Evaluation error: ${err}`);
|
|
455
455
|
}
|
|
456
456
|
};
|
|
457
|
-
const
|
|
457
|
+
const evaluateHue = () => {
|
|
458
458
|
if (/^-?(?:\d+|\d*\.\d+)(?:deg|rad|grad|turn)$/.test(token)) {
|
|
459
|
-
return
|
|
459
|
+
return parseHue(token);
|
|
460
460
|
}
|
|
461
461
|
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
462
462
|
return parseFloat(token);
|
|
463
463
|
}
|
|
464
464
|
const [min2, max2] = [0, 360];
|
|
465
|
-
if (token[token.length - 1] === "%") {
|
|
466
|
-
if (commaSeparated && supportsLegacy === true) {
|
|
467
|
-
throw new Error("The legacy color syntax does not allow percentages for <angle> components.");
|
|
468
|
-
}
|
|
469
|
-
if (relative === true) {
|
|
470
|
-
throw new Error("The relative color syntax doesn't allow percentages for <angle> components.");
|
|
471
|
-
}
|
|
472
|
-
return parsePercent(token, min2, max2);
|
|
473
|
-
}
|
|
474
465
|
if (token.slice(0, 5) === "calc(") {
|
|
475
466
|
return parseCalc(token, min2, max2);
|
|
476
467
|
}
|
|
477
|
-
throw new Error(`Invalid
|
|
468
|
+
throw new Error(`Invalid hue value: '${token}'. Must be a number, or a number with a unit (deg, rad, grad, turn).`);
|
|
478
469
|
};
|
|
479
470
|
const evaluatePercent = () => {
|
|
480
471
|
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
@@ -506,11 +497,11 @@ var Saturon = (() => {
|
|
|
506
497
|
throw new Error(`Invalid number value: '${token}'. Must be a number${relative === false ? " or a percentage" : ""}.`);
|
|
507
498
|
};
|
|
508
499
|
if (token === "none")
|
|
509
|
-
return
|
|
500
|
+
return NaN;
|
|
510
501
|
if (token in base)
|
|
511
502
|
return base[token];
|
|
512
|
-
if (value === "
|
|
513
|
-
return
|
|
503
|
+
if (value === "hue")
|
|
504
|
+
return evaluateHue();
|
|
514
505
|
if (value === "percentage")
|
|
515
506
|
return evaluatePercent();
|
|
516
507
|
if (Array.isArray(value))
|
|
@@ -555,7 +546,7 @@ var Saturon = (() => {
|
|
|
555
546
|
if (commaSeparated && token === "none") {
|
|
556
547
|
throw new Error(`${fn}() cannot use "none" in comma-separated syntax.`);
|
|
557
548
|
}
|
|
558
|
-
if (meta.index !== 3 && meta.value !== "
|
|
549
|
+
if (meta.index !== 3 && meta.value !== "hue" && meta.value !== "percentage" && token.slice(0, 5) !== "calc(") {
|
|
559
550
|
percentFlags.push(token.trim()[token.length - 1] === "%");
|
|
560
551
|
}
|
|
561
552
|
if (token) {
|
|
@@ -772,8 +763,8 @@ var Saturon = (() => {
|
|
|
772
763
|
parse: (str) => {
|
|
773
764
|
const tokens = tokenize(str);
|
|
774
765
|
const ast = getAST(tokens);
|
|
775
|
-
const
|
|
776
|
-
return [...
|
|
766
|
+
const coords = parseAST(ast);
|
|
767
|
+
return [...coords.slice(0, 3), coords[3] ?? 1];
|
|
777
768
|
},
|
|
778
769
|
fromBridge: (coords) => [...fromBridge(coords), coords[3] ?? 1],
|
|
779
770
|
format: ([c1, c2, c3, a = 1], options = {}) => {
|
|
@@ -781,11 +772,12 @@ var Saturon = (() => {
|
|
|
781
772
|
const clipped = fit([c1, c2, c3], name, { method: fitMethod, precision });
|
|
782
773
|
const alpha = Number(min(max(a, 0), 1).toFixed(3)).toString();
|
|
783
774
|
const formatted = clipped.map((v, index) => {
|
|
775
|
+
v = isNaN(v) ? 0 : v;
|
|
784
776
|
if ((units || legacy) && components) {
|
|
785
777
|
const def = Object.values(components).find((comp) => comp.index === index);
|
|
786
778
|
if (def?.value === "percentage")
|
|
787
779
|
return `${v}%`;
|
|
788
|
-
if (def?.value === "
|
|
780
|
+
if (def?.value === "hue" && units)
|
|
789
781
|
return `${v}deg`;
|
|
790
782
|
}
|
|
791
783
|
return v.toString();
|
|
@@ -815,6 +807,7 @@ var Saturon = (() => {
|
|
|
815
807
|
}
|
|
816
808
|
|
|
817
809
|
// dist/math.js
|
|
810
|
+
var EPSILON = 1e-5;
|
|
818
811
|
var MATRICES = {
|
|
819
812
|
D50_to_D65: [
|
|
820
813
|
[0.955473421488075, -0.02309845494876471, 0.06325924320057072],
|
|
@@ -1027,14 +1020,16 @@ var Saturon = (() => {
|
|
|
1027
1020
|
const [r, g, b] = rgb.map((v) => v / 255);
|
|
1028
1021
|
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
1029
1022
|
const L = (max + min) / 2;
|
|
1030
|
-
let H = 0, S = 0;
|
|
1031
1023
|
const d = max - min;
|
|
1032
|
-
|
|
1024
|
+
let H = NaN, S = 0;
|
|
1025
|
+
if (d > 0) {
|
|
1033
1026
|
S = d / (1 - Math.abs(2 * L - 1));
|
|
1034
1027
|
H = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
1035
|
-
H
|
|
1028
|
+
H = H * 60 % 360;
|
|
1036
1029
|
}
|
|
1037
|
-
|
|
1030
|
+
const S_out = S * 100, L_out = L * 100;
|
|
1031
|
+
const invalid = L_out <= EPSILON || 100 - L_out <= EPSILON || S_out <= EPSILON;
|
|
1032
|
+
return [invalid ? NaN : H, S_out, L_out];
|
|
1038
1033
|
}
|
|
1039
1034
|
function HWB_to_RGB([h, w, b]) {
|
|
1040
1035
|
w /= 100;
|
|
@@ -1049,11 +1044,9 @@ var Saturon = (() => {
|
|
|
1049
1044
|
function RGB_to_HWB(rgb) {
|
|
1050
1045
|
const [r, g, b] = rgb.map((v) => v / 255);
|
|
1051
1046
|
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
1052
|
-
let H = 0;
|
|
1053
1047
|
const d = max - min;
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
else {
|
|
1048
|
+
let H = NaN;
|
|
1049
|
+
if (d > EPSILON) {
|
|
1057
1050
|
H = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
1058
1051
|
H = H * 60 % 360;
|
|
1059
1052
|
}
|
|
@@ -1085,7 +1078,7 @@ var Saturon = (() => {
|
|
|
1085
1078
|
let H = Math.atan2(b, a) * 180 / Math.PI;
|
|
1086
1079
|
if (H < 0)
|
|
1087
1080
|
H += 360;
|
|
1088
|
-
return [L, C, H];
|
|
1081
|
+
return [L, C, C <= EPSILON ? NaN : H];
|
|
1089
1082
|
}
|
|
1090
1083
|
function OKLAB_to_XYZD65(oklab) {
|
|
1091
1084
|
const { LMS_to_XYZD65, OKLAB_to_LMS } = MATRICES;
|
|
@@ -1105,7 +1098,7 @@ var Saturon = (() => {
|
|
|
1105
1098
|
let H = Math.atan2(b, a) * 180 / Math.PI;
|
|
1106
1099
|
if (H < 0)
|
|
1107
1100
|
H += 360;
|
|
1108
|
-
return [L, C, H];
|
|
1101
|
+
return [L, C, C <= EPSILON ? NaN : H];
|
|
1109
1102
|
}
|
|
1110
1103
|
|
|
1111
1104
|
// dist/converters.js
|
|
@@ -1423,7 +1416,7 @@ var Saturon = (() => {
|
|
|
1423
1416
|
supportsLegacy: true,
|
|
1424
1417
|
alphaVariant: "hsla",
|
|
1425
1418
|
components: {
|
|
1426
|
-
h: { index: 0, value: "
|
|
1419
|
+
h: { index: 0, value: "hue", precision: 0 },
|
|
1427
1420
|
s: { index: 1, value: "percentage", precision: 0 },
|
|
1428
1421
|
l: { index: 2, value: "percentage", precision: 0 }
|
|
1429
1422
|
},
|
|
@@ -1433,7 +1426,7 @@ var Saturon = (() => {
|
|
|
1433
1426
|
},
|
|
1434
1427
|
hwb: {
|
|
1435
1428
|
components: {
|
|
1436
|
-
h: { index: 0, value: "
|
|
1429
|
+
h: { index: 0, value: "hue", precision: 0 },
|
|
1437
1430
|
w: { index: 1, value: "percentage", precision: 0 },
|
|
1438
1431
|
b: { index: 2, value: "percentage", precision: 0 }
|
|
1439
1432
|
},
|
|
@@ -1457,7 +1450,7 @@ var Saturon = (() => {
|
|
|
1457
1450
|
components: {
|
|
1458
1451
|
l: { index: 0, value: "percentage", precision: 5 },
|
|
1459
1452
|
c: { index: 1, value: [0, 150], precision: 5 },
|
|
1460
|
-
h: { index: 2, value: "
|
|
1453
|
+
h: { index: 2, value: "hue", precision: 5 }
|
|
1461
1454
|
},
|
|
1462
1455
|
bridge: "lab",
|
|
1463
1456
|
toBridge: LCH_to_LAB,
|
|
@@ -1479,7 +1472,7 @@ var Saturon = (() => {
|
|
|
1479
1472
|
components: {
|
|
1480
1473
|
l: { index: 0, value: [0, 1], precision: 5 },
|
|
1481
1474
|
c: { index: 1, value: [0, 0.4], precision: 5 },
|
|
1482
|
-
h: { index: 2, value: "
|
|
1475
|
+
h: { index: 2, value: "hue", precision: 5 }
|
|
1483
1476
|
},
|
|
1484
1477
|
bridge: "oklab",
|
|
1485
1478
|
toBridge: OKLCH_to_OKLAB,
|
|
@@ -1500,14 +1493,9 @@ var Saturon = (() => {
|
|
|
1500
1493
|
const HEX = str.slice(1);
|
|
1501
1494
|
if (![3, 4, 6, 8].includes(HEX.length))
|
|
1502
1495
|
throw new Error("Invalid hex color length.");
|
|
1503
|
-
for (const ch of HEX)
|
|
1504
|
-
|
|
1505
|
-
const isDigit = code >= 48 && code <= 57;
|
|
1506
|
-
const isLower = code >= 97 && code <= 102;
|
|
1507
|
-
const isUpper = code >= 65 && code <= 70;
|
|
1508
|
-
if (!(isDigit || isLower || isUpper))
|
|
1496
|
+
for (const ch of HEX)
|
|
1497
|
+
if (!/^[0-9a-fA-F]$/.test(ch))
|
|
1509
1498
|
throw new Error("Invalid hex color character.");
|
|
1510
|
-
}
|
|
1511
1499
|
const expand = (c) => parseInt(c.length === 1 ? c + c : c, 16);
|
|
1512
1500
|
const [r, g, b, a = 255] = HEX.length <= 4 ? HEX.split("").map(expand) : [HEX.slice(0, 2), HEX.slice(2, 4), HEX.slice(4, 6), HEX.slice(6, 8)].map((c) => parseInt(c || "ff", 16));
|
|
1513
1501
|
return [r, g, b, a / 255];
|
|
@@ -1667,7 +1655,7 @@ var Saturon = (() => {
|
|
|
1667
1655
|
if (!comps) {
|
|
1668
1656
|
throw new Error(`Unknown color model: ${model}.`);
|
|
1669
1657
|
}
|
|
1670
|
-
const hasHue = Object.values(comps).some((c) => c.value === "
|
|
1658
|
+
const hasHue = Object.values(comps).some((c) => c.value === "hue");
|
|
1671
1659
|
if (!hasHue) {
|
|
1672
1660
|
throw new Error(`Hue interpolation not supported in ${model} space.`);
|
|
1673
1661
|
}
|
|
@@ -1677,14 +1665,14 @@ var Saturon = (() => {
|
|
|
1677
1665
|
const { color: color1, weight: weight1 } = extractColorAndWeight(parts[1]);
|
|
1678
1666
|
const { color: color2, weight: weight2 } = extractColorAndWeight(parts[2]);
|
|
1679
1667
|
const { amount, alphaMultiplier = 1 } = getWeight2Prime(weight1, weight2);
|
|
1680
|
-
return color1.with({ alpha: (a) => a * alphaMultiplier }).in(model).mix(color2.with({ alpha: (a) => a * alphaMultiplier }), { amount, hue }).in("rgb").
|
|
1668
|
+
return color1.with({ alpha: (a) => isNaN(a) ? a : a * alphaMultiplier }, false).in(model).mix(color2.with({ alpha: (a) => isNaN(a) ? a : a * alphaMultiplier }, false), { amount, hue }).in("rgb").coords;
|
|
1681
1669
|
}
|
|
1682
1670
|
},
|
|
1683
1671
|
transparent: {
|
|
1684
1672
|
isValid: (str) => str === "transparent",
|
|
1685
1673
|
bridge: "rgb",
|
|
1686
1674
|
toBridge: (coords) => coords,
|
|
1687
|
-
parse: (str) => [
|
|
1675
|
+
parse: (str) => [NaN, NaN, NaN, 0]
|
|
1688
1676
|
// eslint-disable-line no-unused-vars
|
|
1689
1677
|
}
|
|
1690
1678
|
};
|
|
@@ -1714,7 +1702,7 @@ var Saturon = (() => {
|
|
|
1714
1702
|
toBridge: (coords) => coords,
|
|
1715
1703
|
parse: (str) => {
|
|
1716
1704
|
const inner = str.slice(15, -1);
|
|
1717
|
-
const [, luminance] = Color.from(inner).in("xyz-d65").
|
|
1705
|
+
const [, luminance] = Color.from(inner).in("xyz-d65").coords;
|
|
1718
1706
|
return luminance > 0.5 ? [0, 0, 0, 1] : [255, 255, 255, 1];
|
|
1719
1707
|
}
|
|
1720
1708
|
},
|
|
@@ -1807,7 +1795,7 @@ var Saturon = (() => {
|
|
|
1807
1795
|
if (idx < tokens.length && tokens[idx] === ",") {
|
|
1808
1796
|
idx++;
|
|
1809
1797
|
const fallbackStr = tokens.slice(idx).join(" ");
|
|
1810
|
-
return Color.from(fallbackStr).in("rgb").
|
|
1798
|
+
return Color.from(fallbackStr).in("rgb").coords;
|
|
1811
1799
|
}
|
|
1812
1800
|
const red = 1 - Math.min(1, c * (1 - k) + k);
|
|
1813
1801
|
const green = 1 - Math.min(1, m * (1 - k) + k);
|
|
@@ -1878,7 +1866,7 @@ var Saturon = (() => {
|
|
|
1878
1866
|
}
|
|
1879
1867
|
const [color1, color2] = parts;
|
|
1880
1868
|
const { theme } = config;
|
|
1881
|
-
return Color.from(theme === "light" ? color1 : color2).
|
|
1869
|
+
return Color.from(theme === "light" ? color1 : color2).coords;
|
|
1882
1870
|
}
|
|
1883
1871
|
}
|
|
1884
1872
|
};
|
|
@@ -1989,7 +1977,7 @@ var Saturon = (() => {
|
|
|
1989
1977
|
const v = Math.random() || 1e-9;
|
|
1990
1978
|
value = base + Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * dev;
|
|
1991
1979
|
} else {
|
|
1992
|
-
let [min, max] = comp.value === "
|
|
1980
|
+
let [min, max] = comp.value === "hue" ? [0, 360] : comp.value === "percentage" ? [0, 100] : comp.value;
|
|
1993
1981
|
const limits = options.limits?.[name];
|
|
1994
1982
|
if (limits) {
|
|
1995
1983
|
const [lMin, lMax] = limits;
|
|
@@ -2002,7 +1990,7 @@ var Saturon = (() => {
|
|
|
2002
1990
|
r = biasFn(r);
|
|
2003
1991
|
value = min + r * (max - min);
|
|
2004
1992
|
}
|
|
2005
|
-
if (comp.value === "
|
|
1993
|
+
if (comp.value === "hue")
|
|
2006
1994
|
value = (value % 360 + 360) % 360;
|
|
2007
1995
|
else if (comp.value === "percentage")
|
|
2008
1996
|
value = Math.min(100, Math.max(0, value));
|
|
@@ -2052,7 +2040,6 @@ var Saturon = (() => {
|
|
|
2052
2040
|
cache.set("paths", paths);
|
|
2053
2041
|
}
|
|
2054
2042
|
const key = `${from}-${to}`;
|
|
2055
|
-
const coords = this.coords.slice(0, 3);
|
|
2056
2043
|
if (!paths.has(key)) {
|
|
2057
2044
|
const queue = [from], parent = { [from]: null };
|
|
2058
2045
|
for (let i = 0; i < queue.length; i++) {
|
|
@@ -2075,7 +2062,7 @@ var Saturon = (() => {
|
|
|
2075
2062
|
}
|
|
2076
2063
|
paths.set(key, path2);
|
|
2077
2064
|
}
|
|
2078
|
-
let value = [...coords];
|
|
2065
|
+
let value = [...this.coords.map((v) => isNaN(v) ? 0 : v)];
|
|
2079
2066
|
const path = paths.get(key);
|
|
2080
2067
|
for (let i = 0; i < path.length - 1; i++) {
|
|
2081
2068
|
const a = path[i], b = path[i + 1];
|
|
@@ -2141,7 +2128,7 @@ var Saturon = (() => {
|
|
|
2141
2128
|
};
|
|
2142
2129
|
const normalize = (c, i) => {
|
|
2143
2130
|
const v = Object.values(defs)[i]?.value;
|
|
2144
|
-
const [min, max] = Array.isArray(v) ? v : v === "
|
|
2131
|
+
const [min, max] = Array.isArray(v) ? v : v === "hue" ? [0, 360] : [0, 100];
|
|
2145
2132
|
if (Number.isNaN(c))
|
|
2146
2133
|
return 0;
|
|
2147
2134
|
if (c === Infinity)
|
|
@@ -2155,10 +2142,10 @@ var Saturon = (() => {
|
|
|
2155
2142
|
method,
|
|
2156
2143
|
precision
|
|
2157
2144
|
});
|
|
2158
|
-
return [...fitted
|
|
2145
|
+
return [...fitted, coords[3]];
|
|
2159
2146
|
}
|
|
2160
2147
|
/**
|
|
2161
|
-
*
|
|
2148
|
+
* Creates a new Color instance with modified component values.
|
|
2162
2149
|
*
|
|
2163
2150
|
* This method supports several flexible update styles:
|
|
2164
2151
|
*
|
|
@@ -2197,66 +2184,60 @@ var Saturon = (() => {
|
|
|
2197
2184
|
* color.with(({ r, g, b }) => [r * 0.5, g * 0.5, b * 0.5, 1]);
|
|
2198
2185
|
* ```
|
|
2199
2186
|
*
|
|
2200
|
-
* @
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2205
|
-
* @
|
|
2187
|
+
* @template M - The color model type
|
|
2188
|
+
* @param values - The new component values to apply. Can be:
|
|
2189
|
+
* - A partial object mapping component names to numbers or update functions
|
|
2190
|
+
* - A function that receives current components and returns partial updates or an array of values
|
|
2191
|
+
* - An array of new values corresponding to component indices
|
|
2192
|
+
* @param normalize - Whether to normalize component values to their valid ranges. Defaults to `true`.
|
|
2193
|
+
* When `false`, values are not clamped or validated against their ranges.
|
|
2194
|
+
* @returns A new Color instance with the updated component values
|
|
2206
2195
|
*/
|
|
2207
2196
|
/* eslint-disable no-unused-vars */
|
|
2208
|
-
with(values) {
|
|
2197
|
+
with(values, normalize = true) {
|
|
2198
|
+
const normRange = (defValue) => Array.isArray(defValue) ? defValue : defValue === "hue" ? [0, 360] : [0, 100];
|
|
2199
|
+
const normalizeComponent = (c, defValue) => {
|
|
2200
|
+
if (normalize === false)
|
|
2201
|
+
return c;
|
|
2202
|
+
const [min, max] = normRange(defValue);
|
|
2203
|
+
if (Number.isNaN(c))
|
|
2204
|
+
return 0;
|
|
2205
|
+
if (c === Infinity)
|
|
2206
|
+
return max;
|
|
2207
|
+
if (c === -Infinity)
|
|
2208
|
+
return min;
|
|
2209
|
+
return c;
|
|
2210
|
+
};
|
|
2209
2211
|
const { model } = this;
|
|
2210
|
-
const coords = this.
|
|
2211
|
-
const { components } = colorModels[model];
|
|
2212
|
-
if (!components)
|
|
2213
|
-
throw new Error(`Model ${model} does not have defined components.`);
|
|
2212
|
+
const coords = this.coords.slice();
|
|
2214
2213
|
const defs = {
|
|
2215
|
-
...components,
|
|
2214
|
+
...colorModels[model].components,
|
|
2215
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
2216
2216
|
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
2217
2217
|
};
|
|
2218
2218
|
const names = Object.keys(defs);
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
} else {
|
|
2224
|
-
newValues = values;
|
|
2225
|
-
}
|
|
2219
|
+
const newValues = typeof values === "function" ? values(Object.fromEntries(names.map((k) => {
|
|
2220
|
+
const def = defs[k];
|
|
2221
|
+
return [k, normalizeComponent(coords[def.index], def.value)];
|
|
2222
|
+
}))) : values;
|
|
2226
2223
|
if (Array.isArray(newValues)) {
|
|
2227
2224
|
const adjusted = coords.map((curr, i) => {
|
|
2228
2225
|
const incoming = newValues[i];
|
|
2229
|
-
const { value } = Object.values(defs).find((d) => d.index === i);
|
|
2230
2226
|
if (typeof incoming !== "number")
|
|
2231
2227
|
return curr;
|
|
2232
|
-
const
|
|
2233
|
-
|
|
2234
|
-
return 0;
|
|
2235
|
-
if (incoming === Infinity)
|
|
2236
|
-
return max;
|
|
2237
|
-
if (incoming === -Infinity)
|
|
2238
|
-
return min;
|
|
2239
|
-
return incoming;
|
|
2228
|
+
const def = Object.values(defs).find((d) => d.index === i);
|
|
2229
|
+
return normalizeComponent(incoming, def.value);
|
|
2240
2230
|
});
|
|
2241
|
-
return new _Color(model, [...adjusted.slice(0, 3), coords[3]]);
|
|
2231
|
+
return new _Color(model, [...adjusted.slice(0, 3), coords[3] ?? 1]);
|
|
2242
2232
|
}
|
|
2243
2233
|
const next = [...coords];
|
|
2244
2234
|
for (const name of names) {
|
|
2245
2235
|
if (!(name in newValues))
|
|
2246
2236
|
continue;
|
|
2247
2237
|
const { index, value } = defs[name];
|
|
2248
|
-
const current = coords[index];
|
|
2249
2238
|
const raw = newValues[name];
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const [min, max] = Array.isArray(value) ? value : value === "angle" ? [0, 360] : [0, 100];
|
|
2253
|
-
if (Number.isNaN(val))
|
|
2254
|
-
val = 0;
|
|
2255
|
-
else if (val === Infinity)
|
|
2256
|
-
val = max;
|
|
2257
|
-
else if (val === -Infinity)
|
|
2258
|
-
val = min;
|
|
2259
|
-
}
|
|
2239
|
+
const prev = normalizeComponent(coords[index], value);
|
|
2240
|
+
const val = typeof raw === "function" ? normalizeComponent(raw(prev), value) : typeof raw === "number" ? normalizeComponent(raw, value) : coords[index];
|
|
2260
2241
|
next[index] = val;
|
|
2261
2242
|
}
|
|
2262
2243
|
return new _Color(model, [...next.slice(0, 3), next[3] ?? coords[3]]);
|
|
@@ -2271,69 +2252,89 @@ var Saturon = (() => {
|
|
|
2271
2252
|
*/
|
|
2272
2253
|
mix(other, options = {}) {
|
|
2273
2254
|
const { model } = this;
|
|
2274
|
-
const
|
|
2255
|
+
const A = this.coords.slice();
|
|
2256
|
+
const B = (typeof other === "string" ? _Color.from(other) : other).in(model).coords.slice();
|
|
2275
2257
|
const { components } = colorModels[model];
|
|
2276
2258
|
if (!components)
|
|
2277
2259
|
throw new Error(`Model ${model} does not have defined components.`);
|
|
2278
|
-
const { hue = "shorter", amount = 0.5, easing = "linear", gamma = 1 } = options;
|
|
2279
2260
|
const defs = {
|
|
2280
2261
|
...components,
|
|
2281
2262
|
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
2282
2263
|
};
|
|
2264
|
+
const normRange = (v) => Array.isArray(v) ? v : v === "hue" ? [0, 360] : [0, 100];
|
|
2265
|
+
const repairPair = (a, b, v) => {
|
|
2266
|
+
const [min, max] = normRange(v);
|
|
2267
|
+
const fixInf = (x) => x === Infinity ? max : x === -Infinity ? min : x;
|
|
2268
|
+
a = fixInf(a);
|
|
2269
|
+
b = fixInf(b);
|
|
2270
|
+
const aNaN = Number.isNaN(a);
|
|
2271
|
+
const bNaN = Number.isNaN(b);
|
|
2272
|
+
if (aNaN && bNaN)
|
|
2273
|
+
return { a: 0, b: 0 };
|
|
2274
|
+
if (aNaN)
|
|
2275
|
+
return { a: b, b };
|
|
2276
|
+
if (bNaN)
|
|
2277
|
+
return { a, b: a };
|
|
2278
|
+
return { a, b, ok: true };
|
|
2279
|
+
};
|
|
2280
|
+
const hueIndex = defs.h?.index ?? -1;
|
|
2281
|
+
for (const key in defs) {
|
|
2282
|
+
const { index, value } = defs[key];
|
|
2283
|
+
if (index > 3)
|
|
2284
|
+
continue;
|
|
2285
|
+
const r = repairPair(A[index], B[index], value);
|
|
2286
|
+
if (r.ok) {
|
|
2287
|
+
const [min, max] = normRange(value);
|
|
2288
|
+
const fixInf = (x) => x === Infinity ? max : x === -Infinity ? min : x;
|
|
2289
|
+
A[index] = fixInf(A[index]);
|
|
2290
|
+
B[index] = fixInf(B[index]);
|
|
2291
|
+
} else {
|
|
2292
|
+
A[index] = r.a;
|
|
2293
|
+
B[index] = r.b;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
const { hue = "shorter", amount = 0.5, easing = "linear", gamma = 1 } = options;
|
|
2297
|
+
const ease = typeof easing === "function" ? easing : EASINGS[easing];
|
|
2298
|
+
const t = ease(Math.min(1, Math.max(0, amount)));
|
|
2299
|
+
const tt = Math.pow(t, 1 / gamma);
|
|
2300
|
+
const wrap = (v) => (v % 360 + 360) % 360;
|
|
2283
2301
|
const hueDelta = (a, b) => {
|
|
2284
|
-
const d = (
|
|
2302
|
+
const d = wrap(b - a);
|
|
2285
2303
|
return d > 180 ? d - 360 : d;
|
|
2286
2304
|
};
|
|
2287
2305
|
const hueDeltaLong = (a, b) => hueDelta(a, b) >= 0 ? hueDelta(a, b) - 360 : hueDelta(a, b) + 360;
|
|
2288
|
-
const
|
|
2289
|
-
const wrapped = (v) => (v % 360 + 360) % 360;
|
|
2306
|
+
const interpHue = (a, b, t2, method) => {
|
|
2290
2307
|
switch (method) {
|
|
2291
2308
|
case "shorter":
|
|
2292
|
-
return
|
|
2309
|
+
return wrap(a + t2 * hueDelta(a, b));
|
|
2293
2310
|
case "longer":
|
|
2294
|
-
return
|
|
2311
|
+
return wrap(a + t2 * hueDeltaLong(a, b));
|
|
2295
2312
|
case "increasing":
|
|
2296
|
-
return
|
|
2313
|
+
return wrap(a * (1 - t2) + (b < a ? b + 360 : b) * t2);
|
|
2297
2314
|
case "decreasing":
|
|
2298
|
-
return
|
|
2299
|
-
default:
|
|
2300
|
-
throw new Error(`Invalid hue interpolation method: ${method}`);
|
|
2315
|
+
return wrap(a * (1 - t2) + (b > a ? b - 360 : b) * t2);
|
|
2301
2316
|
}
|
|
2317
|
+
throw new Error(`Invalid hue interpolation: ${method}`);
|
|
2302
2318
|
};
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
const
|
|
2309
|
-
const
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
return new _Color(model, [...otherCoords, otherAlpha]);
|
|
2316
|
-
if (thisAlpha < 1 || otherAlpha < 1) {
|
|
2317
|
-
const premixed = thisCoords.map((start, i) => {
|
|
2318
|
-
const end = otherCoords[i];
|
|
2319
|
-
if (i === hueIndex)
|
|
2320
|
-
return interpolateHue(start, end, tt, hue);
|
|
2321
|
-
const a = start * thisAlpha;
|
|
2322
|
-
const b = end * otherAlpha;
|
|
2323
|
-
return a * (1 - tt) + b * tt;
|
|
2319
|
+
if (tt === 0)
|
|
2320
|
+
return new _Color(model, [...A]);
|
|
2321
|
+
if (tt === 1)
|
|
2322
|
+
return new _Color(model, [...B]);
|
|
2323
|
+
const aA = A[3];
|
|
2324
|
+
const aB = B[3];
|
|
2325
|
+
const resolvedA = A.slice(0, 3);
|
|
2326
|
+
const resolvedB = B.slice(0, 3);
|
|
2327
|
+
if (aA < 1 || aB < 1) {
|
|
2328
|
+
const premixed = resolvedA.map((a2, i) => {
|
|
2329
|
+
const b = resolvedB[i];
|
|
2330
|
+
return i === hueIndex ? interpHue(a2, b, tt, hue) : a2 * aA * (1 - tt) + b * aB * tt;
|
|
2324
2331
|
});
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2327
|
-
return new _Color(model, [...
|
|
2332
|
+
const a = aA * (1 - tt) + aB * tt;
|
|
2333
|
+
const out = a > 0 ? premixed.map((v, i) => i === hueIndex ? v : v / a) : resolvedA.map((_, i) => i === hueIndex ? premixed[i] : 0);
|
|
2334
|
+
return new _Color(model, [...out, a]);
|
|
2328
2335
|
}
|
|
2329
|
-
const
|
|
2330
|
-
|
|
2331
|
-
if (!entry)
|
|
2332
|
-
return start;
|
|
2333
|
-
const end = otherCoords[i];
|
|
2334
|
-
return entry.value === "angle" ? interpolateHue(start, end, tt, hue) : start + (end - start) * tt;
|
|
2335
|
-
});
|
|
2336
|
-
return new _Color(model, [...mixedCoords, 1]);
|
|
2336
|
+
const mixed = resolvedA.map((a, i) => i === hueIndex ? interpHue(a, resolvedB[i], tt, hue) : a + (resolvedB[i] - a) * tt);
|
|
2337
|
+
return new _Color(model, [...mixed, 1]);
|
|
2337
2338
|
}
|
|
2338
2339
|
/**
|
|
2339
2340
|
* Fits this color within the specified gamut using a given method.
|
|
@@ -2350,6 +2351,17 @@ var Saturon = (() => {
|
|
|
2350
2351
|
const fitted = this.in(g).toArray({ fit: method, precision: null });
|
|
2351
2352
|
return new _Color(g, fitted).in(this.model);
|
|
2352
2353
|
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Creates a new Color instance with values fitted to the color model's gamut.
|
|
2356
|
+
*
|
|
2357
|
+
* @param options - Configuration options for fitting
|
|
2358
|
+
* @returns A new Color instance with fitted values
|
|
2359
|
+
*/
|
|
2360
|
+
fit(options = {}) {
|
|
2361
|
+
const { fit: method = config.defaults.fit, precision } = options;
|
|
2362
|
+
const fitted = this.toArray({ fit: method, precision });
|
|
2363
|
+
return new _Color(this.model, fitted);
|
|
2364
|
+
}
|
|
2353
2365
|
/**
|
|
2354
2366
|
* Calculates the WCAG 2.1 contrast ratio between this color and another.
|
|
2355
2367
|
*
|
|
@@ -2523,8 +2535,10 @@ var Saturon = (() => {
|
|
|
2523
2535
|
*/
|
|
2524
2536
|
equals(other, epsilon = 1e-5) {
|
|
2525
2537
|
const o = typeof other === "string" ? _Color.from(other) : other;
|
|
2538
|
+
const thisCoords = this.toArray({ fit: "none", precision: null });
|
|
2539
|
+
const otherCoords = o.toArray({ fit: "none", precision: null });
|
|
2526
2540
|
if (o.model === this.model)
|
|
2527
|
-
return
|
|
2541
|
+
return thisCoords.every((v, i) => Math.abs(v - otherCoords[i]) <= epsilon);
|
|
2528
2542
|
const a = this.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2529
2543
|
const b = o.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2530
2544
|
return a.every((v, i) => Math.abs(v - b[i]) <= epsilon);
|
|
@@ -2546,7 +2560,7 @@ var Saturon = (() => {
|
|
|
2546
2560
|
const coords = this.in(g).toArray({ fit: "none", precision: null });
|
|
2547
2561
|
return Object.values(components).every(({ index, value }) => {
|
|
2548
2562
|
const v = coords[index];
|
|
2549
|
-
const [min, max] = Array.isArray(value) ? value : value === "
|
|
2563
|
+
const [min, max] = Array.isArray(value) ? value : value === "hue" ? [0, 360] : [0, 100];
|
|
2550
2564
|
return v >= min - epsilon && v <= max + epsilon;
|
|
2551
2565
|
});
|
|
2552
2566
|
}
|