@ariaui/color 0.1.3 → 0.1.4

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.
@@ -0,0 +1,426 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/parse.ts
22
+ function parseOklchChannels(channels) {
23
+ const normalised = channels.trim();
24
+ const [colorPart, alphaPart] = normalised.split("/").map((s) => s.trim());
25
+ const parts = colorPart.split(/\s+/);
26
+ if (parts.length !== 3) {
27
+ throw new Error(
28
+ `Invalid OKLCH channel string: expected "L C H", got "${channels}"`
29
+ );
30
+ }
31
+ const [rawL, rawC, rawH] = parts;
32
+ let l;
33
+ if (rawL.endsWith("%")) {
34
+ l = parseFloat(rawL) / 100;
35
+ } else {
36
+ l = parseFloat(rawL);
37
+ }
38
+ const c = parseFloat(rawC);
39
+ const h = parseFloat(rawH);
40
+ if (Number.isNaN(l) || Number.isNaN(c) || Number.isNaN(h)) {
41
+ throw new Error(
42
+ `Invalid OKLCH channel string: could not parse numbers from "${channels}"`
43
+ );
44
+ }
45
+ let alpha = 1;
46
+ if (alphaPart !== void 0) {
47
+ alpha = parseFloat(alphaPart);
48
+ if (Number.isNaN(alpha)) {
49
+ throw new Error(
50
+ `Invalid OKLCH alpha value: could not parse "${alphaPart}"`
51
+ );
52
+ }
53
+ }
54
+ return { l, c, h, alpha };
55
+ }
56
+ function parseOklchCss(css) {
57
+ const match = css.match(/oklch\(\s*(.+?)\s*\)/i);
58
+ if (!match) {
59
+ throw new Error(`Invalid oklch() CSS string: "${css}"`);
60
+ }
61
+ return parseOklchChannels(match[1]);
62
+ }
63
+ function parseHex(hex) {
64
+ let h = hex.startsWith("#") ? hex.slice(1) : hex;
65
+ if (h.length === 3) {
66
+ h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
67
+ } else if (h.length === 4) {
68
+ h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2] + h[3] + h[3];
69
+ }
70
+ if (h.length !== 6 && h.length !== 8) {
71
+ throw new Error(`Invalid hex color: "${hex}"`);
72
+ }
73
+ if (!/^[0-9a-fA-F]+$/.test(h)) {
74
+ throw new Error(`Invalid hex color: "${hex}"`);
75
+ }
76
+ const r = parseInt(h.slice(0, 2), 16) / 255;
77
+ const g = parseInt(h.slice(2, 4), 16) / 255;
78
+ const b = parseInt(h.slice(4, 6), 16) / 255;
79
+ const alpha = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
80
+ return { r, g, b, alpha };
81
+ }
82
+ function parseRgb(css) {
83
+ const match = css.match(/rgba?\(\s*(.+?)\s*\)/i);
84
+ if (!match) {
85
+ throw new Error(`Invalid rgb()/rgba() CSS string: "${css}"`);
86
+ }
87
+ const inner = match[1];
88
+ if (inner.includes("/")) {
89
+ const [colorPart, alphaPart] = inner.split("/").map((s) => s.trim());
90
+ const [r, g, b] = colorPart.split(/\s+/).map(Number);
91
+ const alpha = parseFloat(alphaPart);
92
+ return { r: r / 255, g: g / 255, b: b / 255, alpha };
93
+ }
94
+ if (inner.includes(",")) {
95
+ const parts2 = inner.split(",").map((s) => parseFloat(s.trim()));
96
+ return {
97
+ r: parts2[0] / 255,
98
+ g: parts2[1] / 255,
99
+ b: parts2[2] / 255,
100
+ alpha: parts2.length === 4 ? parts2[3] : 1
101
+ };
102
+ }
103
+ const parts = inner.split(/\s+/).map(Number);
104
+ if (parts.length !== 3) {
105
+ throw new Error(`Invalid rgb() CSS string: "${css}"`);
106
+ }
107
+ return { r: parts[0] / 255, g: parts[1] / 255, b: parts[2] / 255, alpha: 1 };
108
+ }
109
+
110
+ // src/convert.ts
111
+ function oklchToOklab(c) {
112
+ const hRad = c.h * Math.PI / 180;
113
+ return {
114
+ L: c.l,
115
+ a: c.c * Math.cos(hRad),
116
+ b: c.c * Math.sin(hRad),
117
+ alpha: c.alpha
118
+ };
119
+ }
120
+ function oklabToOklch(lab) {
121
+ const c = Math.sqrt(lab.a * lab.a + lab.b * lab.b);
122
+ let h = Math.atan2(lab.b, lab.a) * 180 / Math.PI;
123
+ if (h < 0) h += 360;
124
+ if (c < 1e-8) h = 0;
125
+ return { l: lab.L, c, h, alpha: lab.alpha };
126
+ }
127
+ function oklabToLinearSrgb(lab) {
128
+ const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
129
+ const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
130
+ const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
131
+ const l = l_ * l_ * l_;
132
+ const m = m_ * m_ * m_;
133
+ const s = s_ * s_ * s_;
134
+ const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
135
+ const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
136
+ const b = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
137
+ return [r, g, b];
138
+ }
139
+ function linearSrgbToOklab(r, g, b) {
140
+ const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
141
+ const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
142
+ const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
143
+ const l_ = Math.cbrt(l);
144
+ const m_ = Math.cbrt(m);
145
+ const s_ = Math.cbrt(s);
146
+ return {
147
+ L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
148
+ a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
149
+ b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
150
+ alpha: 1
151
+ };
152
+ }
153
+ function linearToGamma(c) {
154
+ if (c <= 31308e-7) return 12.92 * c;
155
+ return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
156
+ }
157
+ function gammaToLinear(c) {
158
+ if (c <= 0.04045) return c / 12.92;
159
+ return Math.pow((c + 0.055) / 1.055, 2.4);
160
+ }
161
+ var GAMUT_EPSILON = 1e-6;
162
+ var DELTA_E_TOLERANCE = 0.02;
163
+ var CHROMA_STEP = 0.01;
164
+ function deltaEOK(a, b) {
165
+ const dL = a.L - b.L;
166
+ const da = a.a - b.a;
167
+ const db = a.b - b.b;
168
+ return Math.sqrt(dL * dL + da * da + db * db);
169
+ }
170
+ function isInGamut(r, g, b) {
171
+ return r >= -GAMUT_EPSILON && r <= 1 + GAMUT_EPSILON && g >= -GAMUT_EPSILON && g <= 1 + GAMUT_EPSILON && b >= -GAMUT_EPSILON && b <= 1 + GAMUT_EPSILON;
172
+ }
173
+ function clampChannel(v) {
174
+ return Math.max(0, Math.min(1, v));
175
+ }
176
+ function gamutMapOklchToSrgb(color) {
177
+ if (color.l <= 0) return { r: 0, g: 0, b: 0, alpha: color.alpha };
178
+ if (color.l >= 1) return { r: 1, g: 1, b: 1, alpha: color.alpha };
179
+ const lab = oklchToOklab(color);
180
+ const [lr, lg, lb] = oklabToLinearSrgb(lab);
181
+ if (isInGamut(lr, lg, lb)) {
182
+ return {
183
+ r: linearToGamma(clampChannel(lr)),
184
+ g: linearToGamma(clampChannel(lg)),
185
+ b: linearToGamma(clampChannel(lb)),
186
+ alpha: color.alpha
187
+ };
188
+ }
189
+ let min = 0;
190
+ let max = color.c;
191
+ let mappedChroma = 0;
192
+ const zeroChromaLab = oklchToOklab(__spreadProps(__spreadValues({}, color), { c: 0 }));
193
+ const [zr, zg, zb] = oklabToLinearSrgb(zeroChromaLab);
194
+ if (!isInGamut(zr, zg, zb)) {
195
+ return {
196
+ r: linearToGamma(clampChannel(zr)),
197
+ g: linearToGamma(clampChannel(zg)),
198
+ b: linearToGamma(clampChannel(zb)),
199
+ alpha: color.alpha
200
+ };
201
+ }
202
+ while (max - min > CHROMA_STEP) {
203
+ const mid = (min + max) / 2;
204
+ const testColor = __spreadProps(__spreadValues({}, color), { c: mid });
205
+ const testLab = oklchToOklab(testColor);
206
+ const [tr, tg, tb] = oklabToLinearSrgb(testLab);
207
+ if (isInGamut(tr, tg, tb)) {
208
+ min = mid;
209
+ mappedChroma = mid;
210
+ } else {
211
+ const clampedR = clampChannel(tr);
212
+ const clampedG = clampChannel(tg);
213
+ const clampedB = clampChannel(tb);
214
+ const clampedLab = linearSrgbToOklab(clampedR, clampedG, clampedB);
215
+ const dE = deltaEOK(testLab, clampedLab);
216
+ if (dE <= DELTA_E_TOLERANCE) {
217
+ min = mid;
218
+ mappedChroma = mid;
219
+ } else {
220
+ max = mid;
221
+ }
222
+ }
223
+ }
224
+ const finalLab = oklchToOklab(__spreadProps(__spreadValues({}, color), { c: mappedChroma }));
225
+ const [fr, fg, fb] = oklabToLinearSrgb(finalLab);
226
+ return {
227
+ r: linearToGamma(clampChannel(fr)),
228
+ g: linearToGamma(clampChannel(fg)),
229
+ b: linearToGamma(clampChannel(fb)),
230
+ alpha: color.alpha
231
+ };
232
+ }
233
+ function oklchToSrgb(color) {
234
+ return gamutMapOklchToSrgb(color);
235
+ }
236
+ function srgbToOklch(color) {
237
+ const lr = gammaToLinear(color.r);
238
+ const lg = gammaToLinear(color.g);
239
+ const lb = gammaToLinear(color.b);
240
+ const lab = linearSrgbToOklab(lr, lg, lb);
241
+ return __spreadValues({}, oklabToOklch(__spreadProps(__spreadValues({}, lab), { alpha: color.alpha })));
242
+ }
243
+ function srgbToHex(color) {
244
+ const r = Math.round(clampChannel(color.r) * 255);
245
+ const g = Math.round(clampChannel(color.g) * 255);
246
+ const b = Math.round(clampChannel(color.b) * 255);
247
+ const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
248
+ if (color.alpha < 1) {
249
+ const a = Math.round(clampChannel(color.alpha) * 255);
250
+ return `${hex}${a.toString(16).padStart(2, "0")}`;
251
+ }
252
+ return hex;
253
+ }
254
+ function oklchToHex(color) {
255
+ return srgbToHex(oklchToSrgb(color));
256
+ }
257
+ function hexToOklch(hex) {
258
+ return srgbToOklch(parseHex(hex));
259
+ }
260
+
261
+ // src/manipulate.ts
262
+ function clamp(value, min, max) {
263
+ return Math.max(min, Math.min(max, value));
264
+ }
265
+ function normaliseHue(h) {
266
+ return (h % 360 + 360) % 360;
267
+ }
268
+ function lighten(color, amount) {
269
+ return __spreadProps(__spreadValues({}, color), { l: clamp(color.l + amount, 0, 1) });
270
+ }
271
+ function darken(color, amount) {
272
+ return __spreadProps(__spreadValues({}, color), { l: clamp(color.l - amount, 0, 1) });
273
+ }
274
+ function saturate(color, amount) {
275
+ return __spreadProps(__spreadValues({}, color), { c: Math.max(0, color.c + amount) });
276
+ }
277
+ function desaturate(color, amount) {
278
+ return __spreadProps(__spreadValues({}, color), { c: Math.max(0, color.c - amount) });
279
+ }
280
+ function setAlpha(color, alpha) {
281
+ return __spreadProps(__spreadValues({}, color), { alpha: clamp(alpha, 0, 1) });
282
+ }
283
+ function adjustHue(color, degrees) {
284
+ return __spreadProps(__spreadValues({}, color), { h: normaliseHue(color.h + degrees) });
285
+ }
286
+ function mix(color1, color2, ratio = 0.5) {
287
+ const t = clamp(ratio, 0, 1);
288
+ const l = color1.l + (color2.l - color1.l) * t;
289
+ const c = color1.c + (color2.c - color1.c) * t;
290
+ const alpha = color1.alpha + (color2.alpha - color1.alpha) * t;
291
+ let h1 = normaliseHue(color1.h);
292
+ let h2 = normaliseHue(color2.h);
293
+ let dh = h2 - h1;
294
+ if (dh > 180) dh -= 360;
295
+ if (dh < -180) dh += 360;
296
+ const h = normaliseHue(h1 + dh * t);
297
+ return { l, c, h, alpha };
298
+ }
299
+ function complement(color) {
300
+ return adjustHue(color, 180);
301
+ }
302
+ function invert(color) {
303
+ return __spreadProps(__spreadValues({}, color), { l: 1 - color.l });
304
+ }
305
+ function grayscale(color) {
306
+ return __spreadProps(__spreadValues({}, color), { c: 0, h: 0 });
307
+ }
308
+
309
+ // src/contrast.ts
310
+ function relativeLuminance(color) {
311
+ const toLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
312
+ const r = toLinear(color.r);
313
+ const g = toLinear(color.g);
314
+ const b = toLinear(color.b);
315
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
316
+ }
317
+ function contrastRatio(fg, bg) {
318
+ const fgSrgb = "l" in fg ? oklchToSrgb(fg) : fg;
319
+ const bgSrgb = "l" in bg ? oklchToSrgb(bg) : bg;
320
+ const l1 = relativeLuminance(fgSrgb);
321
+ const l2 = relativeLuminance(bgSrgb);
322
+ const lighter = Math.max(l1, l2);
323
+ const darker = Math.min(l1, l2);
324
+ return (lighter + 0.05) / (darker + 0.05);
325
+ }
326
+ function meetsAA(fg, bg, size = "normal") {
327
+ const ratio = contrastRatio(fg, bg);
328
+ return size === "large" ? ratio >= 3 : ratio >= 4.5;
329
+ }
330
+ function meetsAAA(fg, bg, size = "normal") {
331
+ const ratio = contrastRatio(fg, bg);
332
+ return size === "large" ? ratio >= 4.5 : ratio >= 7;
333
+ }
334
+ function suggestAccessible(fg, bg, target = "AA", size = "normal") {
335
+ const checker = target === "AAA" ? meetsAAA : meetsAA;
336
+ if (checker(fg, bg, size)) return fg;
337
+ const bgSrgb = "l" in bg ? oklchToSrgb(bg) : bg;
338
+ const bgLum = relativeLuminance(bgSrgb);
339
+ const directions = bgLum > 0.5 ? [(l, s) => l - s, (l, s) => l + s] : [(l, s) => l + s, (l, s) => l - s];
340
+ const step = 5e-3;
341
+ for (const adjust of directions) {
342
+ let testL = fg.l;
343
+ for (let i = 0; i < 220; i++) {
344
+ testL = adjust(testL, step);
345
+ if (testL < 0 || testL > 1) break;
346
+ testL = Math.max(0, Math.min(1, testL));
347
+ const candidate = __spreadProps(__spreadValues({}, fg), { l: testL });
348
+ if (checker(candidate, bg, size)) {
349
+ return candidate;
350
+ }
351
+ }
352
+ }
353
+ const blackRatio = contrastRatio({ l: 0, c: 0, h: 0, alpha: 1 }, bg);
354
+ const whiteRatio = contrastRatio({ l: 1, c: 0, h: 0, alpha: 1 }, bg);
355
+ return blackRatio > whiteRatio ? __spreadProps(__spreadValues({}, fg), { l: 0, c: 0 }) : __spreadProps(__spreadValues({}, fg), { l: 1, c: 0 });
356
+ }
357
+
358
+ // src/format.ts
359
+ function toOklchString(color) {
360
+ const l = `${roundTo(color.l * 100, 2)}%`;
361
+ const c = roundTo(color.c, 4);
362
+ const h = roundTo(color.h, 2);
363
+ if (color.alpha < 1) {
364
+ return `oklch(${l} ${c} ${h} / ${roundTo(color.alpha, 3)})`;
365
+ }
366
+ return `oklch(${l} ${c} ${h})`;
367
+ }
368
+ function toRgbString(color) {
369
+ const r = Math.round(clamp01(color.r) * 255);
370
+ const g = Math.round(clamp01(color.g) * 255);
371
+ const b = Math.round(clamp01(color.b) * 255);
372
+ if (color.alpha < 1) {
373
+ return `rgb(${r} ${g} ${b} / ${roundTo(color.alpha, 3)})`;
374
+ }
375
+ return `rgb(${r} ${g} ${b})`;
376
+ }
377
+ function toHexString(color) {
378
+ const srgb = "l" in color ? oklchToSrgb(color) : color;
379
+ return srgbToHex(srgb);
380
+ }
381
+ function toCssChannels(color) {
382
+ const l = `${roundTo(color.l * 100, 2)}%`;
383
+ const c = roundTo(color.c, 4);
384
+ const h = roundTo(color.h, 2);
385
+ return `${l} ${c} ${h}`;
386
+ }
387
+ function roundTo(value, decimals) {
388
+ const factor = Math.pow(10, decimals);
389
+ return Math.round(value * factor) / factor;
390
+ }
391
+ function clamp01(v) {
392
+ return Math.max(0, Math.min(1, v));
393
+ }
394
+
395
+ export {
396
+ __spreadValues,
397
+ parseOklchChannels,
398
+ parseOklchCss,
399
+ parseHex,
400
+ parseRgb,
401
+ oklchToOklab,
402
+ oklabToOklch,
403
+ oklchToSrgb,
404
+ srgbToOklch,
405
+ srgbToHex,
406
+ oklchToHex,
407
+ hexToOklch,
408
+ lighten,
409
+ darken,
410
+ saturate,
411
+ desaturate,
412
+ setAlpha,
413
+ adjustHue,
414
+ mix,
415
+ complement,
416
+ invert,
417
+ grayscale,
418
+ contrastRatio,
419
+ meetsAA,
420
+ meetsAAA,
421
+ suggestAccessible,
422
+ toOklchString,
423
+ toRgbString,
424
+ toHexString,
425
+ toCssChannels
426
+ };