Skip to content

Commit

Permalink
feat: Add expanded support for CSS Colors (#34600)
Browse files Browse the repository at this point in the history
Summary:
## Summary

This PR adds expanded support for CSS Colors, as requested on #34425. It updates the current regex used to match the functional notation for colors, e.g `rgb()` to accept space as a valid separator between values as specified on [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/) definition. This also adds support for the `hwb` notation.

## Changelog

[General] [Added] - Add expanded support for CSS Colors

Pull Request resolved: #34600

Test Plan: We can test different color scenarios through the new test cases added to the already existing normalizeColor-test.js

Reviewed By: necolas

Differential Revision: D39269360

Pulled By: cipolleschi

fbshipit-source-id: 449158d17256bbab8bd9fd0da29245660225fb92
  • Loading branch information
gabrieldonadel authored and facebook-github-bot committed Sep 7, 2022
1 parent 34e7d48 commit ac1fe3b
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 9 deletions.
25 changes: 25 additions & 0 deletions packages/normalize-color/__tests__/normalizeColor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ it('accepts only spec compliant colors', () => {
expect(normalizeColor('#abcdef')).not.toBe(null);
expect(normalizeColor('#abcdef01')).not.toBe(null);
expect(normalizeColor('rgb(1,2,3)')).not.toBe(null);
expect(normalizeColor('rgb(1 2 3)')).not.toBe(null);
expect(normalizeColor('rgb(1, 2, 3)')).not.toBe(null);
expect(normalizeColor('rgb( 1 , 2 , 3 )')).not.toBe(null);
expect(normalizeColor('rgb(-1, -2, -3)')).not.toBe(null);
Expand All @@ -45,6 +46,7 @@ it('refuses non-spec compliant colors', () => {
expect(normalizeColor('rgb 255 0 0')).toBe(null);
expect(normalizeColor('RGBA(0, 1, 2)')).toBe(null);
expect(normalizeColor('rgb (0, 1, 2)')).toBe(null);
expect(normalizeColor('rgba(0 0 0 0.0)')).toBe(null);
expect(normalizeColor('hsv(0, 1, 2)')).toBe(null);
// $FlowExpectedError - Intentionally malformed argument.
expect(normalizeColor({r: 10, g: 10, b: 10})).toBe(null);
Expand Down Expand Up @@ -81,6 +83,8 @@ it('handles rgb properly', () => {
expect(normalizeColor('rgb(100, 15, 69)')).toBe(0x640f45ff);
expect(normalizeColor('rgb(255, 255, 255)')).toBe(0xffffffff);
expect(normalizeColor('rgb(256, 256, 256)')).toBe(0xffffffff);
expect(normalizeColor('rgb(0 0 0)')).toBe(0x000000ff);
expect(normalizeColor('rgb(0 0 255)')).toBe(0x0000ffff);
});

it('handles rgba properly', () => {
Expand All @@ -91,6 +95,9 @@ it('handles rgba properly', () => {
expect(normalizeColor('rgba(0, 0, 0, 1)')).toBe(0x000000ff);
expect(normalizeColor('rgba(0, 0, 0, 1.5)')).toBe(0x000000ff);
expect(normalizeColor('rgba(100, 15, 69, 0.5)')).toBe(0x640f4580);
expect(normalizeColor('rgba(0 0 0 / 0.0)')).toBe(0x00000000);
expect(normalizeColor('rgba(0 0 0 / 1)')).toBe(0x000000ff);
expect(normalizeColor('rgba(100 15 69 / 0.5)')).toBe(0x640f4580);
});

it('handles hsl properly', () => {
Expand All @@ -103,13 +110,31 @@ it('handles hsl properly', () => {
expect(normalizeColor('hsl(70, 110%, 75%)')).toBe(0xeaff80ff);
expect(normalizeColor('hsl(70, 0%, 75%)')).toBe(0xbfbfbfff);
expect(normalizeColor('hsl(70, -10%, 75%)')).toBe(0xbfbfbfff);
expect(normalizeColor('hsl(0 0% 0%)')).toBe(0x000000ff);
expect(normalizeColor('hsl(360 100% 100%)')).toBe(0xffffffff);
expect(normalizeColor('hsl(180 50% 50%)')).toBe(0x40bfbfff);
});

it('handles hsla properly', () => {
expect(normalizeColor('hsla(0, 0%, 0%, 0)')).toBe(0x00000000);
expect(normalizeColor('hsla(360, 100%, 100%, 1)')).toBe(0xffffffff);
expect(normalizeColor('hsla(360, 100%, 100%, 0)')).toBe(0xffffff00);
expect(normalizeColor('hsla(180, 50%, 50%, 0.2)')).toBe(0x40bfbf33);
expect(normalizeColor('hsla(0 0% 0% / 0)')).toBe(0x00000000);
expect(normalizeColor('hsla(360 100% 100% / 1)')).toBe(0xffffffff);
expect(normalizeColor('hsla(360 100% 100% / 0)')).toBe(0xffffff00);
expect(normalizeColor('hsla(180 50% 50% / 0.2)')).toBe(0x40bfbf33);
});

it('handles hwb properly', () => {
expect(normalizeColor('hwb(0, 0%, 100%)')).toBe(0x000000ff);
expect(normalizeColor('hwb(0, 100%, 0%)')).toBe(0xffffffff);
expect(normalizeColor('hwb(0, 0%, 0%)')).toBe(0xff0000ff);
expect(normalizeColor('hwb(70, 50%, 0%)')).toBe(0xeaff80ff);
expect(normalizeColor('hwb(0, 50%, 50%)')).toBe(0x808080ff);
expect(normalizeColor('hwb(360, 100%, 100%)')).toBe(0x808080ff);
expect(normalizeColor('hwb(0 0% 0%)')).toBe(0xff0000ff);
expect(normalizeColor('hwb(70 50% 0%)')).toBe(0xeaff80ff);
});

it('handles named colors properly', () => {
Expand Down
101 changes: 92 additions & 9 deletions packages/normalize-color/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,23 @@ function normalizeColor(color) {
}

if ((match = matchers.rgba.exec(color))) {
// rgba(R G B / A) notation
if (match[6] !== undefined) {
return (
((parse255(match[6]) << 24) | // r
(parse255(match[7]) << 16) | // g
(parse255(match[8]) << 8) | // b
parse1(match[9])) >>> // a
0
);
}

// rgba(R, G, B, A) notation
return (
((parse255(match[1]) << 24) | // r
(parse255(match[2]) << 16) | // g
(parse255(match[3]) << 8) | // b
parse1(match[4])) >>> // a
((parse255(match[2]) << 24) | // r
(parse255(match[3]) << 16) | // g
(parse255(match[4]) << 8) | // b
parse1(match[5])) >>> // a
0
);
}
Expand Down Expand Up @@ -106,13 +118,39 @@ function normalizeColor(color) {
}

if ((match = matchers.hsla.exec(color))) {
// hsla(H S L / A) notation
if (match[6] !== undefined) {
return (
(hslToRgb(
parse360(match[6]), // h
parsePercentage(match[7]), // s
parsePercentage(match[8]), // l
) |
parse1(match[9])) >>> // a
0
);
}

// hsla(H, S, L, A) notation
return (
(hslToRgb(
parse360(match[2]), // h
parsePercentage(match[3]), // s
parsePercentage(match[4]), // l
) |
parse1(match[5])) >>> // a
0
);
}

if ((match = matchers.hwb.exec(color))) {
return (
(hwbToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]), // l
parsePercentage(match[2]), // w
parsePercentage(match[3]), // b
) |
parse1(match[4])) >>> // a
0x000000ff) >>> // a
0
);
}
Expand Down Expand Up @@ -153,10 +191,42 @@ function hslToRgb(h, s, l) {
);
}

function hwbToRgb(h, w, b) {
if (w + b >= 1) {
const gray = Math.round((w * 255) / (w + b));

return (gray << 24) | (gray << 16) | (gray << 8);
}

const red = hue2rgb(0, 1, h + 1 / 3) * (1 - w - b) + w;
const green = hue2rgb(0, 1, h) * (1 - w - b) + w;
const blue = hue2rgb(0, 1, h - 1 / 3) * (1 - w - b) + w;

return (
(Math.round(red * 255) << 24) |
(Math.round(green * 255) << 16) |
(Math.round(blue * 255) << 8)
);
}

const NUMBER = '[-+]?\\d*\\.?\\d+';
const PERCENTAGE = NUMBER + '%';

function call(...args) {
return '\\(\\s*(' + args.join(')\\s*,?\\s*(') + ')\\s*\\)';
}

function callWithSlashSeparator(...args) {
return (
'\\(\\s*(' +
args.slice(0, args.length - 1).join(')\\s*,?\\s*(') +
')\\s*/\\s*(' +
args[args.length - 1] +
')\\s*\\)'
);
}

function commaSeparatedCall(...args) {
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)';
}

Expand All @@ -166,9 +236,22 @@ function getMatchers() {
if (cachedMatchers === undefined) {
cachedMatchers = {
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
rgba: new RegExp(
'rgba(' +
commaSeparatedCall(NUMBER, NUMBER, NUMBER, NUMBER) +
'|' +
callWithSlashSeparator(NUMBER, NUMBER, NUMBER, NUMBER) +
')',
),
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
hsla: new RegExp(
'hsla(' +
commaSeparatedCall(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) +
'|' +
callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) +
')',
),
hwb: new RegExp('hwb' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#([0-9a-fA-F]{6})$/,
Expand Down

0 comments on commit ac1fe3b

Please sign in to comment.