Skip to content

Commit a226236

Browse files
gabrieldonadelOlimpiaZurek
authored andcommitted
feat: Add expanded support for CSS Colors (facebook#34600)
Summary: ## Summary This PR adds expanded support for CSS Colors, as requested on facebook#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: facebook#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
1 parent 798d96a commit a226236

File tree

2 files changed

+117
-9
lines changed

2 files changed

+117
-9
lines changed

packages/normalize-color/__tests__/normalizeColor-test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ it('accepts only spec compliant colors', () => {
1919
expect(normalizeColor('#abcdef')).not.toBe(null);
2020
expect(normalizeColor('#abcdef01')).not.toBe(null);
2121
expect(normalizeColor('rgb(1,2,3)')).not.toBe(null);
22+
expect(normalizeColor('rgb(1 2 3)')).not.toBe(null);
2223
expect(normalizeColor('rgb(1, 2, 3)')).not.toBe(null);
2324
expect(normalizeColor('rgb( 1 , 2 , 3 )')).not.toBe(null);
2425
expect(normalizeColor('rgb(-1, -2, -3)')).not.toBe(null);
@@ -45,6 +46,7 @@ it('refuses non-spec compliant colors', () => {
4546
expect(normalizeColor('rgb 255 0 0')).toBe(null);
4647
expect(normalizeColor('RGBA(0, 1, 2)')).toBe(null);
4748
expect(normalizeColor('rgb (0, 1, 2)')).toBe(null);
49+
expect(normalizeColor('rgba(0 0 0 0.0)')).toBe(null);
4850
expect(normalizeColor('hsv(0, 1, 2)')).toBe(null);
4951
// $FlowExpectedError - Intentionally malformed argument.
5052
expect(normalizeColor({r: 10, g: 10, b: 10})).toBe(null);
@@ -81,6 +83,8 @@ it('handles rgb properly', () => {
8183
expect(normalizeColor('rgb(100, 15, 69)')).toBe(0x640f45ff);
8284
expect(normalizeColor('rgb(255, 255, 255)')).toBe(0xffffffff);
8385
expect(normalizeColor('rgb(256, 256, 256)')).toBe(0xffffffff);
86+
expect(normalizeColor('rgb(0 0 0)')).toBe(0x000000ff);
87+
expect(normalizeColor('rgb(0 0 255)')).toBe(0x0000ffff);
8488
});
8589

8690
it('handles rgba properly', () => {
@@ -91,6 +95,9 @@ it('handles rgba properly', () => {
9195
expect(normalizeColor('rgba(0, 0, 0, 1)')).toBe(0x000000ff);
9296
expect(normalizeColor('rgba(0, 0, 0, 1.5)')).toBe(0x000000ff);
9397
expect(normalizeColor('rgba(100, 15, 69, 0.5)')).toBe(0x640f4580);
98+
expect(normalizeColor('rgba(0 0 0 / 0.0)')).toBe(0x00000000);
99+
expect(normalizeColor('rgba(0 0 0 / 1)')).toBe(0x000000ff);
100+
expect(normalizeColor('rgba(100 15 69 / 0.5)')).toBe(0x640f4580);
94101
});
95102

96103
it('handles hsl properly', () => {
@@ -103,13 +110,31 @@ it('handles hsl properly', () => {
103110
expect(normalizeColor('hsl(70, 110%, 75%)')).toBe(0xeaff80ff);
104111
expect(normalizeColor('hsl(70, 0%, 75%)')).toBe(0xbfbfbfff);
105112
expect(normalizeColor('hsl(70, -10%, 75%)')).toBe(0xbfbfbfff);
113+
expect(normalizeColor('hsl(0 0% 0%)')).toBe(0x000000ff);
114+
expect(normalizeColor('hsl(360 100% 100%)')).toBe(0xffffffff);
115+
expect(normalizeColor('hsl(180 50% 50%)')).toBe(0x40bfbfff);
106116
});
107117

108118
it('handles hsla properly', () => {
109119
expect(normalizeColor('hsla(0, 0%, 0%, 0)')).toBe(0x00000000);
110120
expect(normalizeColor('hsla(360, 100%, 100%, 1)')).toBe(0xffffffff);
111121
expect(normalizeColor('hsla(360, 100%, 100%, 0)')).toBe(0xffffff00);
112122
expect(normalizeColor('hsla(180, 50%, 50%, 0.2)')).toBe(0x40bfbf33);
123+
expect(normalizeColor('hsla(0 0% 0% / 0)')).toBe(0x00000000);
124+
expect(normalizeColor('hsla(360 100% 100% / 1)')).toBe(0xffffffff);
125+
expect(normalizeColor('hsla(360 100% 100% / 0)')).toBe(0xffffff00);
126+
expect(normalizeColor('hsla(180 50% 50% / 0.2)')).toBe(0x40bfbf33);
127+
});
128+
129+
it('handles hwb properly', () => {
130+
expect(normalizeColor('hwb(0, 0%, 100%)')).toBe(0x000000ff);
131+
expect(normalizeColor('hwb(0, 100%, 0%)')).toBe(0xffffffff);
132+
expect(normalizeColor('hwb(0, 0%, 0%)')).toBe(0xff0000ff);
133+
expect(normalizeColor('hwb(70, 50%, 0%)')).toBe(0xeaff80ff);
134+
expect(normalizeColor('hwb(0, 50%, 50%)')).toBe(0x808080ff);
135+
expect(normalizeColor('hwb(360, 100%, 100%)')).toBe(0x808080ff);
136+
expect(normalizeColor('hwb(0 0% 0%)')).toBe(0xff0000ff);
137+
expect(normalizeColor('hwb(70 50% 0%)')).toBe(0xeaff80ff);
113138
});
114139

115140
it('handles named colors properly', () => {

packages/normalize-color/index.js

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,23 @@ function normalizeColor(color) {
4848
}
4949

5050
if ((match = matchers.rgba.exec(color))) {
51+
// rgba(R G B / A) notation
52+
if (match[6] !== undefined) {
53+
return (
54+
((parse255(match[6]) << 24) | // r
55+
(parse255(match[7]) << 16) | // g
56+
(parse255(match[8]) << 8) | // b
57+
parse1(match[9])) >>> // a
58+
0
59+
);
60+
}
61+
62+
// rgba(R, G, B, A) notation
5163
return (
52-
((parse255(match[1]) << 24) | // r
53-
(parse255(match[2]) << 16) | // g
54-
(parse255(match[3]) << 8) | // b
55-
parse1(match[4])) >>> // a
64+
((parse255(match[2]) << 24) | // r
65+
(parse255(match[3]) << 16) | // g
66+
(parse255(match[4]) << 8) | // b
67+
parse1(match[5])) >>> // a
5668
0
5769
);
5870
}
@@ -106,13 +118,39 @@ function normalizeColor(color) {
106118
}
107119

108120
if ((match = matchers.hsla.exec(color))) {
121+
// hsla(H S L / A) notation
122+
if (match[6] !== undefined) {
123+
return (
124+
(hslToRgb(
125+
parse360(match[6]), // h
126+
parsePercentage(match[7]), // s
127+
parsePercentage(match[8]), // l
128+
) |
129+
parse1(match[9])) >>> // a
130+
0
131+
);
132+
}
133+
134+
// hsla(H, S, L, A) notation
109135
return (
110136
(hslToRgb(
137+
parse360(match[2]), // h
138+
parsePercentage(match[3]), // s
139+
parsePercentage(match[4]), // l
140+
) |
141+
parse1(match[5])) >>> // a
142+
0
143+
);
144+
}
145+
146+
if ((match = matchers.hwb.exec(color))) {
147+
return (
148+
(hwbToRgb(
111149
parse360(match[1]), // h
112-
parsePercentage(match[2]), // s
113-
parsePercentage(match[3]), // l
150+
parsePercentage(match[2]), // w
151+
parsePercentage(match[3]), // b
114152
) |
115-
parse1(match[4])) >>> // a
153+
0x000000ff) >>> // a
116154
0
117155
);
118156
}
@@ -153,10 +191,42 @@ function hslToRgb(h, s, l) {
153191
);
154192
}
155193

194+
function hwbToRgb(h, w, b) {
195+
if (w + b >= 1) {
196+
const gray = Math.round((w * 255) / (w + b));
197+
198+
return (gray << 24) | (gray << 16) | (gray << 8);
199+
}
200+
201+
const red = hue2rgb(0, 1, h + 1 / 3) * (1 - w - b) + w;
202+
const green = hue2rgb(0, 1, h) * (1 - w - b) + w;
203+
const blue = hue2rgb(0, 1, h - 1 / 3) * (1 - w - b) + w;
204+
205+
return (
206+
(Math.round(red * 255) << 24) |
207+
(Math.round(green * 255) << 16) |
208+
(Math.round(blue * 255) << 8)
209+
);
210+
}
211+
156212
const NUMBER = '[-+]?\\d*\\.?\\d+';
157213
const PERCENTAGE = NUMBER + '%';
158214

159215
function call(...args) {
216+
return '\\(\\s*(' + args.join(')\\s*,?\\s*(') + ')\\s*\\)';
217+
}
218+
219+
function callWithSlashSeparator(...args) {
220+
return (
221+
'\\(\\s*(' +
222+
args.slice(0, args.length - 1).join(')\\s*,?\\s*(') +
223+
')\\s*/\\s*(' +
224+
args[args.length - 1] +
225+
')\\s*\\)'
226+
);
227+
}
228+
229+
function commaSeparatedCall(...args) {
160230
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)';
161231
}
162232

@@ -166,9 +236,22 @@ function getMatchers() {
166236
if (cachedMatchers === undefined) {
167237
cachedMatchers = {
168238
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
169-
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
239+
rgba: new RegExp(
240+
'rgba(' +
241+
commaSeparatedCall(NUMBER, NUMBER, NUMBER, NUMBER) +
242+
'|' +
243+
callWithSlashSeparator(NUMBER, NUMBER, NUMBER, NUMBER) +
244+
')',
245+
),
170246
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
171-
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
247+
hsla: new RegExp(
248+
'hsla(' +
249+
commaSeparatedCall(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) +
250+
'|' +
251+
callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) +
252+
')',
253+
),
254+
hwb: new RegExp('hwb' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
172255
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
173256
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
174257
hex6: /^#([0-9a-fA-F]{6})$/,

0 commit comments

Comments
 (0)