Skip to content

Commit 44a5bc3

Browse files
committed
css-color-parser
1 parent ed4b1d6 commit 44a5bc3

4 files changed

Lines changed: 206 additions & 26 deletions

File tree

packages/css-color-parser/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changes to CSS Color Parser
22

3+
### Unreleased (patch)
4+
5+
- Fix analogous components and sets for `hwb()`
6+
- Align precision of powerless components with the specification
7+
38
### 4.1.0
49

510
_April 12, 2026_

packages/css-color-parser/dist/index.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/css-color-parser/src/color-data.ts

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -292,79 +292,154 @@ export function colorDataTo(colorData: ColorData, toNotation: ColorNotation): Co
292292
case ColorNotation.HSL:
293293
switch (colorData.colorNotation) {
294294
case ColorNotation.HWB:
295-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [0], [1, 2]);
295+
outputColorData.channels = carryForwardMissingComponents(
296+
// HWB
297+
colorData.channels, [0], [1, 2],
298+
// HSL
299+
outputColorData.channels, [0], [1, 2]
300+
);
296301
break;
297302
case ColorNotation.Lab:
298303
case ColorNotation.OKLab:
299-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [2], [0, 1], outputColorData.channels, [0], [1, 2]);
304+
outputColorData.channels = carryForwardMissingComponents(
305+
// LAB
306+
colorData.channels, [0], [1, 2],
307+
// HSL
308+
outputColorData.channels, [2], [0, 1]
309+
);
300310
break;
301311
case ColorNotation.LCH:
302312
case ColorNotation.OKLCH:
303-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0, 1, 2], [], outputColorData.channels, [2, 1, 0], []);
313+
outputColorData.channels = carryForwardMissingComponents(
314+
// LCH
315+
colorData.channels, [0, 1, 2], [],
316+
// HSL
317+
outputColorData.channels, [2, 1, 0], []
318+
);
304319
break;
305320
default:
306-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [], [], outputColorData.channels, [], []);
321+
outputColorData.channels = carryForwardMissingComponents(
322+
colorData.channels, [], [],
323+
outputColorData.channels, [], []
324+
);
307325
}
308326

309327
break;
310328
case ColorNotation.HWB:
311329
switch (colorData.colorNotation) {
312330
case ColorNotation.HSL:
313-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [0], [1, 2]);
331+
outputColorData.channels = carryForwardMissingComponents(
332+
// HSL
333+
colorData.channels, [0], [1, 2],
334+
// HWB
335+
outputColorData.channels, [0], [1, 2]
336+
);
314337
break;
315338
case ColorNotation.LCH:
316339
case ColorNotation.OKLCH:
317-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [2], [0, 1]);
340+
outputColorData.channels = carryForwardMissingComponents(
341+
// LCH
342+
colorData.channels, [2], [0, 1],
343+
// HWB
344+
outputColorData.channels, [0], [1, 2]
345+
);
318346
break;
319347
default:
320-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [], [], outputColorData.channels, [], []);
348+
outputColorData.channels = carryForwardMissingComponents(
349+
colorData.channels, [], [],
350+
outputColorData.channels, [], []
351+
);
321352
}
322353

323354
break;
324355
case ColorNotation.Lab:
325356
case ColorNotation.OKLab:
326357
switch (colorData.colorNotation) {
327358
case ColorNotation.HSL:
328-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [2], [0, 1]);
359+
outputColorData.channels = carryForwardMissingComponents(
360+
// HSL
361+
colorData.channels, [2], [0, 1],
362+
// LAB
363+
outputColorData.channels, [0], [1, 2]
364+
);
329365
break;
330366
case ColorNotation.Lab:
331367
case ColorNotation.OKLab:
332-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0, 1, 2], [], outputColorData.channels, [0, 1, 2], []);
368+
outputColorData.channels = carryForwardMissingComponents(
369+
// LAB
370+
colorData.channels, [0, 1, 2], [],
371+
// LAB
372+
outputColorData.channels, [0, 1, 2], []
373+
);
333374
break;
334375
case ColorNotation.LCH:
335376
case ColorNotation.OKLCH:
336-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [0], [1, 2]);
377+
outputColorData.channels = carryForwardMissingComponents(
378+
// LCH
379+
colorData.channels, [0], [1, 2],
380+
// LAB
381+
outputColorData.channels, [0], [1, 2]
382+
);
337383
break;
338384
default:
339-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [], [], outputColorData.channels, [], []);
385+
outputColorData.channels = carryForwardMissingComponents(
386+
colorData.channels, [], [],
387+
outputColorData.channels, [], []
388+
);
340389
}
341390

342391
break;
343392
case ColorNotation.LCH:
344393
case ColorNotation.OKLCH:
345394
switch (colorData.colorNotation) {
346395
case ColorNotation.HSL:
347-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0, 1, 2], [], outputColorData.channels, [2, 1, 0], []);
396+
outputColorData.channels = carryForwardMissingComponents(
397+
// HSL
398+
colorData.channels, [0, 1, 2], [],
399+
// LCH
400+
outputColorData.channels, [2, 1, 0], []
401+
);
348402
break;
349403
case ColorNotation.HWB:
350-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [2], [0, 1]);
404+
outputColorData.channels = carryForwardMissingComponents(
405+
// HWB
406+
colorData.channels, [0], [1, 2],
407+
// LCH
408+
outputColorData.channels, [2], [0, 1]
409+
);
351410
break;
352411
case ColorNotation.Lab:
353412
case ColorNotation.OKLab:
354-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0], [1, 2], outputColorData.channels, [0], [1, 2]);
413+
outputColorData.channels = carryForwardMissingComponents(
414+
// LAB
415+
colorData.channels, [0], [1, 2],
416+
// LCH
417+
outputColorData.channels, [0], [1, 2]
418+
);
355419
break;
356420
case ColorNotation.LCH:
357421
case ColorNotation.OKLCH:
358-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [0, 1, 2], [], outputColorData.channels, [0, 1, 2], []);
422+
outputColorData.channels = carryForwardMissingComponents(
423+
// LCH
424+
colorData.channels, [0, 1, 2], [],
425+
// LCH
426+
outputColorData.channels, [0, 1, 2], []
427+
);
359428
break;
360429
default:
361-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [], [], outputColorData.channels, [], []);
430+
outputColorData.channels = carryForwardMissingComponents(
431+
colorData.channels, [], [],
432+
outputColorData.channels, [], []
433+
);
362434
}
363435

364436
break;
365437

366438
default:
367-
outputColorData.channels = carryForwardMissingComponents(colorData.channels, [], [], outputColorData.channels, [], []);
439+
outputColorData.channels = carryForwardMissingComponents(
440+
colorData.channels, [], [],
441+
outputColorData.channels, [], []
442+
);
368443
}
369444
}
370445

@@ -378,25 +453,25 @@ function convertPowerlessComponentsToMissingComponents(a: Color, colorNotation:
378453

379454
switch (colorNotation) {
380455
case ColorNotation.HSL:
381-
if (!Number.isNaN(out[1]) && reducePrecision(out[1], 4) <= 0) {
456+
if (!Number.isNaN(out[1]) && out[1] <= 0.001) {
382457
out[0] = Number.NaN;
383458
}
384459

385460
break;
386461
case ColorNotation.HWB:
387-
if ((Math.max(0, reducePrecision(out[1], 4)) + Math.max(0, reducePrecision(out[2], 4))) >= 100) {
462+
if (!Number.isNaN(out[1]) && !Number.isNaN(out[2]) && (Math.max(0, out[1]) + Math.max(0, out[2])) >= 99.999) {
388463
out[0] = Number.NaN;
389464
}
390465

391466
break;
392467
case ColorNotation.LCH:
393-
if (!Number.isNaN(out[1]) && reducePrecision(out[1], 4) <= 0) {
468+
if (!Number.isNaN(out[1]) && out[1] <= 0.0015) {
394469
out[2] = Number.NaN;
395470
}
396471

397472
break;
398473
case ColorNotation.OKLCH:
399-
if (!Number.isNaN(out[1]) && reducePrecision(out[1], 6) <= 0) {
474+
if (!Number.isNaN(out[1]) && out[1] <= 0.000004) {
400475
out[2] = Number.NaN;
401476
}
402477

@@ -416,13 +491,13 @@ export function convertPowerlessComponentsToZeroValuesForDisplay(a: Color, color
416491
out[1] = Number.NaN;
417492
}
418493

419-
if (reducePrecision(out[1]) <= 0) {
494+
if (!Number.isNaN(out[1]) && out[1] <= 0.001) {
420495
out[0] = Number.NaN;
421496
}
422497

423498
break;
424499
case ColorNotation.HWB:
425-
if ((Math.max(0, reducePrecision(out[1])) + Math.max(0, reducePrecision(out[2]))) >= 100) {
500+
if (!Number.isNaN(out[1]) && !Number.isNaN(out[2]) && (Math.max(0, out[1]) + Math.max(0, out[2])) >= 99.999) {
426501
out[0] = Number.NaN;
427502
}
428503
break;
@@ -433,7 +508,7 @@ export function convertPowerlessComponentsToZeroValuesForDisplay(a: Color, color
433508
}
434509
break;
435510
case ColorNotation.LCH:
436-
if (reducePrecision(out[1]) <= 0) {
511+
if (!Number.isNaN(out[1]) && out[1] <= 0.0015) {
437512
out[2] = Number.NaN;
438513
}
439514

@@ -449,7 +524,7 @@ export function convertPowerlessComponentsToZeroValuesForDisplay(a: Color, color
449524
}
450525
break;
451526
case ColorNotation.OKLCH:
452-
if (reducePrecision(out[1]) <= 0) {
527+
if (!Number.isNaN(out[1]) && out[1] <= 0.000004) {
453528
out[2] = Number.NaN;
454529
}
455530

packages/css-color-parser/test/basic/basic.mjs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,103 @@ assert.equal(
224224
colorDataFitsDisplayP3_Gamut(color(parse('color(display-p3 0.99 1.0001 0.99)'))),
225225
false,
226226
);
227+
228+
assert.deepStrictEqual(
229+
color(parse('hsl(from lab(50% none none) h s l)')),
230+
{
231+
colorNotation: 'hsl',
232+
channels: [NaN, NaN, 46.632660928353715],
233+
alpha: 1,
234+
syntaxFlags: new Set(['relative-color-syntax', 'has-number-values']),
235+
},
236+
);
237+
238+
assert.deepStrictEqual(
239+
color(parse('lab(from hsl(none none 50%) l a b)')),
240+
{
241+
colorNotation: 'lab',
242+
channels: [53.38896474111432, NaN, NaN],
243+
alpha: 1,
244+
syntaxFlags: new Set(['relative-color-syntax', 'has-number-values']),
245+
},
246+
);
247+
248+
assert.deepStrictEqual(
249+
color(parse('hwb(from lch(none none 50deg) h w b)')),
250+
{
251+
colorNotation: 'hwb',
252+
channels: [NaN, NaN, NaN],
253+
alpha: 1,
254+
syntaxFlags: new Set(['relative-color-syntax', 'has-number-values']),
255+
},
256+
);
257+
258+
assert.deepStrictEqual(
259+
color(parse('lch(from hwb(50deg none none) l c h)')),
260+
{
261+
colorNotation: 'lch',
262+
channels: [NaN, NaN, 87.26522367839932],
263+
alpha: 1,
264+
syntaxFlags: new Set(['relative-color-syntax', 'has-number-values']),
265+
},
266+
);
267+
268+
assert.deepStrictEqual(
269+
color(parse('lab(from oklch(50% none none) l a b')),
270+
{
271+
colorNotation: 'lab',
272+
channels: [42, NaN, NaN],
273+
alpha: 1,
274+
syntaxFlags: new Set(['relative-color-syntax', 'has-number-values']),
275+
},
276+
);
277+
278+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
279+
assert.deepStrictEqual(
280+
color(parse(`lab(from ${a}(none none none) l a b)`)).channels,
281+
[NaN, NaN, NaN],
282+
);
283+
}
284+
285+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
286+
assert.deepStrictEqual(
287+
color(parse(`lch(from ${a}(none none none) l c h)`)).channels,
288+
[NaN, NaN, NaN],
289+
);
290+
}
291+
292+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
293+
assert.deepStrictEqual(
294+
color(parse(`oklab(from ${a}(none none none) l a b)`)).channels,
295+
[NaN, NaN, NaN],
296+
);
297+
}
298+
299+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
300+
assert.deepStrictEqual(
301+
color(parse(`oklch(from ${a}(none none none) l c h)`)).channels,
302+
[NaN, NaN, NaN],
303+
);
304+
}
305+
306+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
307+
assert.deepStrictEqual(
308+
color(parse(`rgb(from ${a}(none none none) r g b)`)).channels,
309+
[NaN, NaN, NaN],
310+
);
311+
}
312+
313+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
314+
assert.deepStrictEqual(
315+
color(parse(`hwb(from ${a}(none none none) h w b)`)).channels,
316+
[NaN, NaN, NaN],
317+
);
318+
}
319+
320+
for (const a of ['lab', 'lch', 'oklab', 'oklch', 'rgb', 'hwb', 'hsl']) {
321+
assert.deepStrictEqual(
322+
color(parse(`hsl(from ${a}(none none none) h s l)`)).channels,
323+
[NaN, NaN, NaN],
324+
);
325+
}
326+

0 commit comments

Comments
 (0)