diff --git a/packages/normalize-color/__tests__/normalizeColor-test.js b/packages/normalize-color/__tests__/normalizeColor-test.js index 257ae1db0cbcfd..6f50aa62490fc9 100644 --- a/packages/normalize-color/__tests__/normalizeColor-test.js +++ b/packages/normalize-color/__tests__/normalizeColor-test.js @@ -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); @@ -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); @@ -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', () => { @@ -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', () => { @@ -103,6 +110,9 @@ 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', () => { @@ -110,6 +120,21 @@ it('handles hsla properly', () => { 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', () => { diff --git a/packages/normalize-color/index.js b/packages/normalize-color/index.js index a20b1c3486d43f..611baaffc13891 100644 --- a/packages/normalize-color/index.js +++ b/packages/normalize-color/index.js @@ -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 ); } @@ -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 ); } @@ -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*\\)'; } @@ -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})$/,