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 CHANGED
@@ -1,4 +1,4 @@
1
- import type { Component, MixOptions, ColorType, OutputType, FormattingOptions, NamedColor, ColorSpace, FitMethod, ColorModel, RandomOptions, GetOptions } from "./types.js";
1
+ import type { Component, MixOptions, ColorType, OutputType, FormattingOptions, NamedColor, ColorSpace, FitMethod, ColorModel, RandomOptions, ComponentOptions } from "./types.js";
2
2
  /**
3
3
  * The `Color` class represents a dynamic CSS color object, allowing for the manipulation
4
4
  * and retrieval of colors in various formats (e.g., RGB, HEX, HSL).
@@ -82,7 +82,7 @@ export declare class Color<M extends ColorModel = ColorModel> {
82
82
  * @returns An object mapping each component (and alpha) to its numeric value.
83
83
  * @throws If the model has no defined components.
84
84
  */
85
- toObject(options?: GetOptions): { [key in Component<M>]: number; };
85
+ toObject(options?: ComponentOptions): { [key in Component<M>]: number; };
86
86
  /**
87
87
  * Returns the color as an array of component values, optionally normalized and fitted.
88
88
  *
@@ -90,9 +90,9 @@ export declare class Color<M extends ColorModel = ColorModel> {
90
90
  * @returns An array of normalized color components and alpha.
91
91
  * @throws If the model has no defined components.
92
92
  */
93
- toArray(options?: GetOptions): number[];
93
+ toArray(options?: ComponentOptions): number[];
94
94
  /**
95
- * Returns a new `Color` instance with updated or replaced component values.
95
+ * Creates a new Color instance with modified component values.
96
96
  *
97
97
  * This method supports several flexible update styles:
98
98
  *
@@ -131,12 +131,14 @@ export declare class Color<M extends ColorModel = ColorModel> {
131
131
  * color.with(({ r, g, b }) => [r * 0.5, g * 0.5, b * 0.5, 1]);
132
132
  * ```
133
133
  *
134
- * @param values - Either:
135
- * - a partial object of component values,
136
- * - an updater function returning an object or array,
137
- * - or an array of new coordinates.
138
- * @returns A new `Color` instance with updated values.
139
- * @throws If the color model has no defined components.
134
+ * @template M - The color model type
135
+ * @param values - The new component values to apply. Can be:
136
+ * - A partial object mapping component names to numbers or update functions
137
+ * - A function that receives current components and returns partial updates or an array of values
138
+ * - An array of new values corresponding to component indices
139
+ * @param normalize - Whether to normalize component values to their valid ranges. Defaults to `true`.
140
+ * When `false`, values are not clamped or validated against their ranges.
141
+ * @returns A new Color instance with the updated component values
140
142
  */
141
143
  with(values: Partial<{
142
144
  [K in Component<M> | "alpha"]: number | ((prev: number) => number);
@@ -144,7 +146,7 @@ export declare class Color<M extends ColorModel = ColorModel> {
144
146
  [K in Component<M> | "alpha"]: number;
145
147
  }) => Partial<{
146
148
  [K in Component<M> | "alpha"]: number;
147
- }> | (number | undefined)[]) | (number | undefined)[]): Color<M>;
149
+ }> | (number | undefined)[]) | (number | undefined)[], normalize?: boolean): Color<M>;
148
150
  /**
149
151
  * Mixes this color with another by a specified amount.
150
152
  *
@@ -163,6 +165,13 @@ export declare class Color<M extends ColorModel = ColorModel> {
163
165
  * @throws If the gamut is unsupported.
164
166
  */
165
167
  within(gamut: ColorSpace, method?: FitMethod): Color<M>;
168
+ /**
169
+ * Creates a new Color instance with values fitted to the color model's gamut.
170
+ *
171
+ * @param options - Configuration options for fitting
172
+ * @returns A new Color instance with fitted values
173
+ */
174
+ fit(options?: ComponentOptions): Color<M>;
166
175
  /**
167
176
  * Calculates the WCAG 2.1 contrast ratio between this color and another.
168
177
  *
package/dist/Color.js CHANGED
@@ -115,7 +115,7 @@ export class Color {
115
115
  value = base + Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * dev;
116
116
  }
117
117
  else {
118
- let [min, max] = comp.value === "angle" ? [0, 360] : comp.value === "percentage" ? [0, 100] : comp.value;
118
+ let [min, max] = comp.value === "hue" ? [0, 360] : comp.value === "percentage" ? [0, 100] : comp.value;
119
119
  const limits = options.limits?.[name];
120
120
  if (limits) {
121
121
  const [lMin, lMax] = limits;
@@ -128,7 +128,7 @@ export class Color {
128
128
  r = biasFn(r);
129
129
  value = min + r * (max - min);
130
130
  }
131
- if (comp.value === "angle")
131
+ if (comp.value === "hue")
132
132
  value = ((value % 360) + 360) % 360;
133
133
  else if (comp.value === "percentage")
134
134
  value = Math.min(100, Math.max(0, value));
@@ -179,7 +179,6 @@ export class Color {
179
179
  cache.set("paths", paths);
180
180
  }
181
181
  const key = `${from}-${to}`;
182
- const coords = this.coords.slice(0, 3);
183
182
  if (!paths.has(key)) {
184
183
  const queue = [from], parent = { [from]: null };
185
184
  for (let i = 0; i < queue.length; i++) {
@@ -202,7 +201,7 @@ export class Color {
202
201
  }
203
202
  paths.set(key, path);
204
203
  }
205
- let value = [...coords];
204
+ let value = [...this.coords.map((v) => (isNaN(v) ? 0 : v))];
206
205
  const path = paths.get(key);
207
206
  for (let i = 0; i < path.length - 1; i++) {
208
207
  const a = path[i], b = path[i + 1];
@@ -268,7 +267,7 @@ export class Color {
268
267
  };
269
268
  const normalize = (c, i) => {
270
269
  const v = Object.values(defs)[i]?.value;
271
- const [min, max] = Array.isArray(v) ? v : v === "angle" ? [0, 360] : [0, 100];
270
+ const [min, max] = Array.isArray(v) ? v : v === "hue" ? [0, 360] : [0, 100];
272
271
  if (Number.isNaN(c))
273
272
  return 0;
274
273
  if (c === Infinity)
@@ -282,10 +281,10 @@ export class Color {
282
281
  method: method,
283
282
  precision,
284
283
  });
285
- return [...fitted.slice(0, 3), coords[3]];
284
+ return [...fitted, coords[3]];
286
285
  }
287
286
  /**
288
- * Returns a new `Color` instance with updated or replaced component values.
287
+ * Creates a new Color instance with modified component values.
289
288
  *
290
289
  * This method supports several flexible update styles:
291
290
  *
@@ -324,68 +323,65 @@ export class Color {
324
323
  * color.with(({ r, g, b }) => [r * 0.5, g * 0.5, b * 0.5, 1]);
325
324
  * ```
326
325
  *
327
- * @param values - Either:
328
- * - a partial object of component values,
329
- * - an updater function returning an object or array,
330
- * - or an array of new coordinates.
331
- * @returns A new `Color` instance with updated values.
332
- * @throws If the color model has no defined components.
326
+ * @template M - The color model type
327
+ * @param values - The new component values to apply. Can be:
328
+ * - A partial object mapping component names to numbers or update functions
329
+ * - A function that receives current components and returns partial updates or an array of values
330
+ * - An array of new values corresponding to component indices
331
+ * @param normalize - Whether to normalize component values to their valid ranges. Defaults to `true`.
332
+ * When `false`, values are not clamped or validated against their ranges.
333
+ * @returns A new Color instance with the updated component values
333
334
  */
334
335
  /* eslint-disable no-unused-vars */
335
- with(values) {
336
- /* eslint-enable no-unused-vars */
336
+ with(values, normalize = true) {
337
+ const normRange = (defValue) => Array.isArray(defValue) ? defValue : defValue === "hue" ? [0, 360] : [0, 100];
338
+ const normalizeComponent = (c, defValue) => {
339
+ if (normalize === false)
340
+ return c;
341
+ const [min, max] = normRange(defValue);
342
+ if (Number.isNaN(c))
343
+ return 0;
344
+ if (c === Infinity)
345
+ return max;
346
+ if (c === -Infinity)
347
+ return min;
348
+ return c;
349
+ };
337
350
  const { model } = this;
338
- const coords = this.toArray({ fit: "none", precision: null });
339
- const { components } = colorModels[model];
340
- if (!components)
341
- throw new Error(`Model ${model} does not have defined components.`);
351
+ const coords = this.coords.slice();
342
352
  const defs = {
343
- ...components,
353
+ ...colorModels[model].components, // eslint-disable-line @typescript-eslint/no-explicit-any
344
354
  alpha: { index: 3, value: [0, 1], precision: 3 },
345
355
  };
346
356
  const names = Object.keys(defs);
347
- let newValues;
348
- if (typeof values === "function") {
349
- const result = values(Object.fromEntries(names.map((c) => [c, coords[defs[c].index]])));
350
- newValues = result;
351
- }
352
- else {
353
- newValues = values;
354
- }
357
+ const newValues = typeof values === "function"
358
+ ? values(Object.fromEntries(names.map((k) => {
359
+ const def = defs[k];
360
+ return [k, normalizeComponent(coords[def.index], def.value)];
361
+ })))
362
+ : values;
355
363
  if (Array.isArray(newValues)) {
356
364
  const adjusted = coords.map((curr, i) => {
357
365
  const incoming = newValues[i];
358
- const { value } = Object.values(defs).find((d) => d.index === i);
359
366
  if (typeof incoming !== "number")
360
367
  return curr;
361
- const [min, max] = Array.isArray(value) ? value : value === "angle" ? [0, 360] : [0, 100];
362
- if (Number.isNaN(incoming))
363
- return 0;
364
- if (incoming === Infinity)
365
- return max;
366
- if (incoming === -Infinity)
367
- return min;
368
- return incoming;
368
+ const def = Object.values(defs).find((d) => d.index === i);
369
+ return normalizeComponent(incoming, def.value);
369
370
  });
370
- return new Color(model, [...adjusted.slice(0, 3), coords[3]]);
371
+ return new Color(model, [...adjusted.slice(0, 3), coords[3] ?? 1]);
371
372
  }
372
373
  const next = [...coords];
373
374
  for (const name of names) {
374
375
  if (!(name in newValues))
375
376
  continue;
376
377
  const { index, value } = defs[name];
377
- const current = coords[index];
378
378
  const raw = newValues[name];
379
- let val = typeof raw === "function" ? raw(current) : raw;
380
- if (typeof val === "number") {
381
- const [min, max] = Array.isArray(value) ? value : value === "angle" ? [0, 360] : [0, 100];
382
- if (Number.isNaN(val))
383
- val = 0;
384
- else if (val === Infinity)
385
- val = max;
386
- else if (val === -Infinity)
387
- val = min;
388
- }
379
+ const prev = normalizeComponent(coords[index], value);
380
+ const val = typeof raw === "function"
381
+ ? normalizeComponent(raw(prev), value)
382
+ : typeof raw === "number"
383
+ ? normalizeComponent(raw, value)
384
+ : coords[index];
389
385
  next[index] = val;
390
386
  }
391
387
  return new Color(model, [...next.slice(0, 3), next[3] ?? coords[3]]);
@@ -400,71 +396,92 @@ export class Color {
400
396
  */
401
397
  mix(other, options = {}) {
402
398
  const { model } = this;
403
- const coords = this.toArray({ fit: "none", precision: null });
404
- const { components } = colorModels[model];
399
+ const A = this.coords.slice();
400
+ const B = (typeof other === "string" ? Color.from(other) : other).in(model).coords.slice();
401
+ const { components } = colorModels[model]; // eslint-disable-line @typescript-eslint/no-explicit-any
405
402
  if (!components)
406
403
  throw new Error(`Model ${model} does not have defined components.`);
407
- const { hue = "shorter", amount = 0.5, easing = "linear", gamma = 1.0 } = options;
408
404
  const defs = {
409
405
  ...components,
410
406
  alpha: { index: 3, value: [0, 1], precision: 3 },
411
407
  };
408
+ const normRange = (v) => Array.isArray(v) ? v : v === "hue" ? [0, 360] : [0, 100];
409
+ const repairPair = (a, b, v) => {
410
+ const [min, max] = normRange(v);
411
+ const fixInf = (x) => (x === Infinity ? max : x === -Infinity ? min : x);
412
+ a = fixInf(a);
413
+ b = fixInf(b);
414
+ const aNaN = Number.isNaN(a);
415
+ const bNaN = Number.isNaN(b);
416
+ if (aNaN && bNaN)
417
+ return { a: 0, b: 0 };
418
+ if (aNaN)
419
+ return { a: b, b };
420
+ if (bNaN)
421
+ return { a, b: a };
422
+ return { a, b, ok: true };
423
+ };
424
+ const hueIndex = defs.h?.index ?? -1;
425
+ for (const key in defs) {
426
+ const { index, value } = defs[key];
427
+ if (index > 3)
428
+ continue;
429
+ const r = repairPair(A[index], B[index], value);
430
+ if (r.ok) {
431
+ const [min, max] = normRange(value);
432
+ const fixInf = (x) => (x === Infinity ? max : x === -Infinity ? min : x);
433
+ A[index] = fixInf(A[index]);
434
+ B[index] = fixInf(B[index]);
435
+ }
436
+ else {
437
+ A[index] = r.a;
438
+ B[index] = r.b;
439
+ }
440
+ }
441
+ const { hue = "shorter", amount = 0.5, easing = "linear", gamma = 1.0 } = options;
442
+ const ease = typeof easing === "function" ? easing : EASINGS[easing];
443
+ const t = ease(Math.min(1, Math.max(0, amount)));
444
+ const tt = Math.pow(t, 1 / gamma);
445
+ const wrap = (v) => ((v % 360) + 360) % 360;
412
446
  const hueDelta = (a, b) => {
413
- const d = (((b - a) % 360) + 360) % 360;
447
+ const d = wrap(b - a);
414
448
  return d > 180 ? d - 360 : d;
415
449
  };
416
450
  const hueDeltaLong = (a, b) => hueDelta(a, b) >= 0 ? hueDelta(a, b) - 360 : hueDelta(a, b) + 360;
417
- const interpolateHue = (a, b, t, method) => {
418
- const wrapped = (v) => ((v % 360) + 360) % 360;
451
+ const interpHue = (a, b, t, method) => {
419
452
  switch (method) {
420
453
  case "shorter":
421
- return wrapped(a + t * hueDelta(a, b));
454
+ return wrap(a + t * hueDelta(a, b));
422
455
  case "longer":
423
- return wrapped(a + t * hueDeltaLong(a, b));
456
+ return wrap(a + t * hueDeltaLong(a, b));
424
457
  case "increasing":
425
- return wrapped(a * (1 - t) + (b < a ? b + 360 : b) * t);
458
+ return wrap(a * (1 - t) + (b < a ? b + 360 : b) * t);
426
459
  case "decreasing":
427
- return wrapped(a * (1 - t) + (b > a ? b - 360 : b) * t);
428
- default:
429
- throw new Error(`Invalid hue interpolation method: ${method}`);
460
+ return wrap(a * (1 - t) + (b > a ? b - 360 : b) * t);
430
461
  }
462
+ throw new Error(`Invalid hue interpolation: ${method}`);
431
463
  };
432
- const t = Math.max(0, Math.min(1, amount));
433
- const ease = typeof easing === "function" ? easing : EASINGS[easing];
434
- const tt = Math.pow(ease(t), 1 / gamma);
435
- const otherColor = (typeof other === "string" ? Color.from(other) : other);
436
- const thisCoords = coords.slice(0, 3);
437
- const otherCoords = otherColor.in(model).toArray({ fit: "none", precision: null }).slice(0, 3);
438
- const thisAlpha = coords[3];
439
- const otherAlpha = otherColor.coords[3];
440
- const hueIndex = Object.entries(defs).find(([k]) => k === "h")?.[1].index;
441
- if (t === 0)
442
- return new Color(model, [...thisCoords, thisAlpha]);
443
- if (t === 1)
444
- return new Color(model, [...otherCoords, otherAlpha]);
445
- if (thisAlpha < 1 || otherAlpha < 1) {
446
- const premixed = thisCoords.map((start, i) => {
447
- const end = otherCoords[i];
448
- if (i === hueIndex)
449
- return interpolateHue(start, end, tt, hue);
450
- const a = start * thisAlpha;
451
- const b = end * otherAlpha;
452
- return a * (1 - tt) + b * tt;
464
+ if (tt === 0)
465
+ return new Color(model, [...A]);
466
+ if (tt === 1)
467
+ return new Color(model, [...B]);
468
+ const aA = A[3];
469
+ const aB = B[3];
470
+ const resolvedA = A.slice(0, 3);
471
+ const resolvedB = B.slice(0, 3);
472
+ if (aA < 1 || aB < 1) {
473
+ const premixed = resolvedA.map((a, i) => {
474
+ const b = resolvedB[i];
475
+ return i === hueIndex ? interpHue(a, b, tt, hue) : a * aA * (1 - tt) + b * aB * tt;
453
476
  });
454
- const mixedAlpha = thisAlpha * (1 - tt) + otherAlpha * tt;
455
- const mixed = mixedAlpha > 0
456
- ? premixed.map((c, i) => (i === hueIndex ? c : c / mixedAlpha))
457
- : thisCoords.map((_, i) => (i === hueIndex ? premixed[i] : 0));
458
- return new Color(model, [...mixed, mixedAlpha]);
477
+ const a = aA * (1 - tt) + aB * tt;
478
+ const out = a > 0
479
+ ? premixed.map((v, i) => (i === hueIndex ? v : v / a))
480
+ : resolvedA.map((_, i) => (i === hueIndex ? premixed[i] : 0));
481
+ return new Color(model, [...out, a]);
459
482
  }
460
- const mixedCoords = thisCoords.map((start, i) => {
461
- const entry = Object.values(defs).find((d) => d.index === i);
462
- if (!entry)
463
- return start;
464
- const end = otherCoords[i];
465
- return entry.value === "angle" ? interpolateHue(start, end, tt, hue) : start + (end - start) * tt;
466
- });
467
- return new Color(model, [...mixedCoords, 1]);
483
+ const mixed = resolvedA.map((a, i) => i === hueIndex ? interpHue(a, resolvedB[i], tt, hue) : a + (resolvedB[i] - a) * tt);
484
+ return new Color(model, [...mixed, 1]);
468
485
  }
469
486
  /**
470
487
  * Fits this color within the specified gamut using a given method.
@@ -481,6 +498,17 @@ export class Color {
481
498
  const fitted = this.in(g).toArray({ fit: method, precision: null });
482
499
  return new Color(g, fitted).in(this.model);
483
500
  }
501
+ /**
502
+ * Creates a new Color instance with values fitted to the color model's gamut.
503
+ *
504
+ * @param options - Configuration options for fitting
505
+ * @returns A new Color instance with fitted values
506
+ */
507
+ fit(options = {}) {
508
+ const { fit: method = config.defaults.fit, precision } = options;
509
+ const fitted = this.toArray({ fit: method, precision });
510
+ return new Color(this.model, fitted);
511
+ }
484
512
  /**
485
513
  * Calculates the WCAG 2.1 contrast ratio between this color and another.
486
514
  *
@@ -657,8 +685,10 @@ export class Color {
657
685
  */
658
686
  equals(other, epsilon = 1e-5) {
659
687
  const o = typeof other === "string" ? Color.from(other) : other;
688
+ const thisCoords = this.toArray({ fit: "none", precision: null });
689
+ const otherCoords = o.toArray({ fit: "none", precision: null });
660
690
  if (o.model === this.model)
661
- return this.coords.every((v, i) => Math.abs(v - o.coords[i]) <= epsilon);
691
+ return thisCoords.every((v, i) => Math.abs(v - otherCoords[i]) <= epsilon);
662
692
  const a = this.in("xyz-d65").toArray({ fit: "none", precision: null });
663
693
  const b = o.in("xyz-d65").toArray({ fit: "none", precision: null });
664
694
  return a.every((v, i) => Math.abs(v - b[i]) <= epsilon);
@@ -680,7 +710,7 @@ export class Color {
680
710
  const coords = this.in(g).toArray({ fit: "none", precision: null });
681
711
  return Object.values(components).every(({ index, value }) => {
682
712
  const v = coords[index];
683
- const [min, max] = Array.isArray(value) ? value : value === "angle" ? [0, 360] : [0, 100];
713
+ const [min, max] = Array.isArray(value) ? value : value === "hue" ? [0, 360] : [0, 100];
684
714
  return v >= min - epsilon && v <= max + epsilon;
685
715
  });
686
716
  }
@@ -342,7 +342,7 @@ export declare const colorModels: {
342
342
  readonly components: {
343
343
  readonly h: {
344
344
  readonly index: 0;
345
- readonly value: "angle";
345
+ readonly value: "hue";
346
346
  readonly precision: 0;
347
347
  };
348
348
  readonly s: {
@@ -364,7 +364,7 @@ export declare const colorModels: {
364
364
  readonly components: {
365
365
  readonly h: {
366
366
  readonly index: 0;
367
- readonly value: "angle";
367
+ readonly value: "hue";
368
368
  readonly precision: 0;
369
369
  };
370
370
  readonly w: {
@@ -420,7 +420,7 @@ export declare const colorModels: {
420
420
  };
421
421
  readonly h: {
422
422
  readonly index: 2;
423
- readonly value: "angle";
423
+ readonly value: "hue";
424
424
  readonly precision: 5;
425
425
  };
426
426
  };
@@ -466,7 +466,7 @@ export declare const colorModels: {
466
466
  };
467
467
  readonly h: {
468
468
  readonly index: 2;
469
- readonly value: "angle";
469
+ readonly value: "hue";
470
470
  readonly precision: 5;
471
471
  };
472
472
  };
@@ -331,7 +331,7 @@ export const colorModels = {
331
331
  supportsLegacy: true,
332
332
  alphaVariant: "hsla",
333
333
  components: {
334
- h: { index: 0, value: "angle", precision: 0 },
334
+ h: { index: 0, value: "hue", precision: 0 },
335
335
  s: { index: 1, value: "percentage", precision: 0 },
336
336
  l: { index: 2, value: "percentage", precision: 0 },
337
337
  },
@@ -341,7 +341,7 @@ export const colorModels = {
341
341
  },
342
342
  hwb: {
343
343
  components: {
344
- h: { index: 0, value: "angle", precision: 0 },
344
+ h: { index: 0, value: "hue", precision: 0 },
345
345
  w: { index: 1, value: "percentage", precision: 0 },
346
346
  b: { index: 2, value: "percentage", precision: 0 },
347
347
  },
@@ -365,7 +365,7 @@ export const colorModels = {
365
365
  components: {
366
366
  l: { index: 0, value: "percentage", precision: 5 },
367
367
  c: { index: 1, value: [0, 150], precision: 5 },
368
- h: { index: 2, value: "angle", precision: 5 },
368
+ h: { index: 2, value: "hue", precision: 5 },
369
369
  },
370
370
  bridge: "lab",
371
371
  toBridge: LCH_to_LAB,
@@ -387,7 +387,7 @@ export const colorModels = {
387
387
  components: {
388
388
  l: { index: 0, value: [0, 1], precision: 5 },
389
389
  c: { index: 1, value: [0, 0.4], precision: 5 },
390
- h: { index: 2, value: "angle", precision: 5 },
390
+ h: { index: 2, value: "hue", precision: 5 },
391
391
  },
392
392
  bridge: "oklab",
393
393
  toBridge: OKLCH_to_OKLAB,
@@ -418,14 +418,9 @@ export const colorBases = {
418
418
  const HEX = str.slice(1);
419
419
  if (![3, 4, 6, 8].includes(HEX.length))
420
420
  throw new Error("Invalid hex color length.");
421
- for (const ch of HEX) {
422
- const code = ch.charCodeAt(0);
423
- const isDigit = code >= 48 && code <= 57;
424
- const isLower = code >= 97 && code <= 102;
425
- const isUpper = code >= 65 && code <= 70;
426
- if (!(isDigit || isLower || isUpper))
421
+ for (const ch of HEX)
422
+ if (!/^[0-9a-fA-F]$/.test(ch))
427
423
  throw new Error("Invalid hex color character.");
428
- }
429
424
  const expand = (c) => parseInt(c.length === 1 ? c + c : c, 16);
430
425
  const [r, g, b, a = 255] = HEX.length <= 4
431
426
  ? HEX.split("").map(expand)
@@ -595,7 +590,7 @@ export const colorBases = {
595
590
  if (!comps) {
596
591
  throw new Error(`Unknown color model: ${model}.`);
597
592
  }
598
- const hasHue = Object.values(comps).some((c) => c.value === "angle");
593
+ const hasHue = Object.values(comps).some((c) => c.value === "hue");
599
594
  if (!hasHue) {
600
595
  throw new Error(`Hue interpolation not supported in ${model} space.`);
601
596
  }
@@ -607,18 +602,17 @@ export const colorBases = {
607
602
  const { color: color2, weight: weight2 } = extractColorAndWeight(parts[2]);
608
603
  const { amount, alphaMultiplier = 1 } = getWeight2Prime(weight1, weight2);
609
604
  return color1
610
- .with({ alpha: (a) => a * alphaMultiplier })
605
+ .with({ alpha: (a) => (isNaN(a) ? a : a * alphaMultiplier) }, false)
611
606
  .in(model)
612
- .mix(color2.with({ alpha: (a) => a * alphaMultiplier }), { amount, hue })
613
- .in("rgb")
614
- .toArray({ fit: "none", precision: null });
607
+ .mix(color2.with({ alpha: (a) => (isNaN(a) ? a : a * alphaMultiplier) }, false), { amount, hue })
608
+ .in("rgb").coords;
615
609
  },
616
610
  },
617
611
  transparent: {
618
612
  isValid: (str) => str === "transparent",
619
613
  bridge: "rgb",
620
614
  toBridge: (coords) => coords,
621
- parse: (str) => [0, 0, 0, 0], // eslint-disable-line no-unused-vars
615
+ parse: (str) => [NaN, NaN, NaN, 0], // eslint-disable-line no-unused-vars
622
616
  },
623
617
  };
624
618
  /**
@@ -651,7 +645,7 @@ export const colorTypes = {
651
645
  toBridge: (coords) => coords,
652
646
  parse: (str) => {
653
647
  const inner = str.slice(15, -1);
654
- const [, luminance] = Color.from(inner).in("xyz-d65").toArray({ fit: "none", precision: null });
648
+ const [, luminance] = Color.from(inner).in("xyz-d65").coords;
655
649
  return luminance > 0.5 ? [0, 0, 0, 1] : [255, 255, 255, 1];
656
650
  },
657
651
  },
@@ -745,7 +739,7 @@ export const colorTypes = {
745
739
  if (idx < tokens.length && tokens[idx] === ",") {
746
740
  idx++;
747
741
  const fallbackStr = tokens.slice(idx).join(" ");
748
- return Color.from(fallbackStr).in("rgb").toArray({ fit: "none", precision: null });
742
+ return Color.from(fallbackStr).in("rgb").coords;
749
743
  }
750
744
  const red = 1 - Math.min(1, c * (1 - k) + k);
751
745
  const green = 1 - Math.min(1, m * (1 - k) + k);
@@ -816,7 +810,7 @@ export const colorTypes = {
816
810
  }
817
811
  const [color1, color2] = parts;
818
812
  const { theme } = config;
819
- return Color.from(theme === "light" ? color1 : color2).toArray({ fit: "none", precision: null });
813
+ return Color.from(theme === "light" ? color1 : color2).coords;
820
814
  },
821
815
  },
822
816
  };