diff --git a/Libraries/StyleSheet/__tests__/normalizeColor-test.js b/Libraries/StyleSheet/__tests__/normalizeColor-test.js index 5421edccd23319..f07498751e2829 100644 --- a/Libraries/StyleSheet/__tests__/normalizeColor-test.js +++ b/Libraries/StyleSheet/__tests__/normalizeColor-test.js @@ -13,13 +13,11 @@ const {OS} = require('../../Utilities/Platform'); const normalizeColor = require('../normalizeColor'); -it('forwards calls to @react-native/normalize-color/base', () => { - jest - .resetModules() - .mock('@react-native/normalize-color/base', () => jest.fn()); +it('forwards calls to @react-native/normalize-color', () => { + jest.resetModules().mock('@react-native/normalize-color', () => jest.fn()); expect(require('../normalizeColor')('#abc')).not.toBe(null); - expect(require('@react-native/normalize-color/base')).toBeCalled(); + expect(require('@react-native/normalize-color')).toBeCalled(); }); describe('iOS', () => { diff --git a/Libraries/StyleSheet/normalizeColor.js b/Libraries/StyleSheet/normalizeColor.js index 4ba0dae0085f11..89a672c4567ddc 100755 --- a/Libraries/StyleSheet/normalizeColor.js +++ b/Libraries/StyleSheet/normalizeColor.js @@ -10,7 +10,7 @@ /* eslint no-bitwise: 0 */ -import normalizeColorBase from '@react-native/normalize-color/base'; +import _normalizeColor from '@react-native/normalize-color'; import type {ColorValue} from './StyleSheet'; import type {ProcessedColorValue} from './processColor'; @@ -27,7 +27,7 @@ function normalizeColor( } if (typeof color === 'string' || typeof color === 'number') { - return normalizeColorBase(color); + return _normalizeColor(color); } } diff --git a/package.json b/package.json index 773a915a7985c1..f8d1e77291223d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@react-native-community/cli-platform-android": "^6.0.0", "@react-native-community/cli-platform-ios": "^6.0.0", "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "1.0.0", + "@react-native/normalize-color": "2.0.0", "@react-native/polyfills": "2.0.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", diff --git a/packages/normalize-color/__tests__/base-test.js b/packages/normalize-color/__tests__/base-test.js deleted file mode 100644 index c37c9af5ff0a7f..00000000000000 --- a/packages/normalize-color/__tests__/base-test.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @emails oncall+react_native - */ - -'use strict'; - -const normalizeColorBase = require('../base'); - -it('should accept only spec compliant colors', () => { - expect(normalizeColorBase('#abc')).not.toBe(null); - expect(normalizeColorBase('#abcd')).not.toBe(null); - expect(normalizeColorBase('#abcdef')).not.toBe(null); - expect(normalizeColorBase('#abcdef01')).not.toBe(null); - expect(normalizeColorBase('rgb(1,2,3)')).not.toBe(null); - expect(normalizeColorBase('rgb(1, 2, 3)')).not.toBe(null); - expect(normalizeColorBase('rgb( 1 , 2 , 3 )')).not.toBe(null); - expect(normalizeColorBase('rgb(-1, -2, -3)')).not.toBe(null); - expect(normalizeColorBase('rgba(0, 0, 0, 1)')).not.toBe(null); - expect(normalizeColorBase(0x01234567 + 0.5)).toBe(null); - expect(normalizeColorBase(-1)).toBe(null); - expect(normalizeColorBase(0xffffffff + 1)).toBe(null); -}); - -it('should temporarily accept floating point values for rgb', () => { - expect(normalizeColorBase('rgb(1.1, 2.1, 3.1)')).toBe(0x010203ff); - expect(normalizeColorBase('rgba(1.1, 2.1, 3.1, 1.0)')).toBe(0x010203ff); -}); - -it('should refuse non spec compliant colors', () => { - expect(normalizeColorBase('#00gg00')).toBe(null); - expect(normalizeColorBase('rgb(1, 2, 3,)')).toBe(null); - expect(normalizeColorBase('rgb(1, 2, 3')).toBe(null); - - // Used to be accepted by normalizeColorBase - expect(normalizeColorBase('abc')).toBe(null); - expect(normalizeColorBase(' #abc ')).toBe(null); - expect(normalizeColorBase('##abc')).toBe(null); - expect(normalizeColorBase('rgb 255 0 0')).toBe(null); - expect(normalizeColorBase('RGBA(0, 1, 2)')).toBe(null); - expect(normalizeColorBase('rgb (0, 1, 2)')).toBe(null); - expect(normalizeColorBase('hsv(0, 1, 2)')).toBe(null); - expect(normalizeColorBase({r: 10, g: 10, b: 10})).toBe(null); - expect(normalizeColorBase('hsl(1%, 2, 3)')).toBe(null); - expect(normalizeColorBase('rgb(1%, 2%, 3%)')).toBe(null); -}); - -it('should handle hex6 properly', () => { - expect(normalizeColorBase('#000000')).toBe(0x000000ff); - expect(normalizeColorBase('#ffffff')).toBe(0xffffffff); - expect(normalizeColorBase('#ff00ff')).toBe(0xff00ffff); - expect(normalizeColorBase('#abcdef')).toBe(0xabcdefff); - expect(normalizeColorBase('#012345')).toBe(0x012345ff); -}); - -it('should handle hex3 properly', () => { - expect(normalizeColorBase('#000')).toBe(0x000000ff); - expect(normalizeColorBase('#fff')).toBe(0xffffffff); - expect(normalizeColorBase('#f0f')).toBe(0xff00ffff); -}); - -it('should handle hex8 properly', () => { - expect(normalizeColorBase('#00000000')).toBe(0x00000000); - expect(normalizeColorBase('#ffffffff')).toBe(0xffffffff); - expect(normalizeColorBase('#ffff00ff')).toBe(0xffff00ff); - expect(normalizeColorBase('#abcdef01')).toBe(0xabcdef01); - expect(normalizeColorBase('#01234567')).toBe(0x01234567); -}); - -it('should handle rgb properly', () => { - expect(normalizeColorBase('rgb(0, 0, 0)')).toBe(0x000000ff); - expect(normalizeColorBase('rgb(-1, -2, -3)')).toBe(0x000000ff); - expect(normalizeColorBase('rgb(0, 0, 255)')).toBe(0x0000ffff); - expect(normalizeColorBase('rgb(100, 15, 69)')).toBe(0x640f45ff); - expect(normalizeColorBase('rgb(255, 255, 255)')).toBe(0xffffffff); - expect(normalizeColorBase('rgb(256, 256, 256)')).toBe(0xffffffff); -}); - -it('should handle rgba properly', () => { - expect(normalizeColorBase('rgba(0, 0, 0, 0.0)')).toBe(0x00000000); - expect(normalizeColorBase('rgba(0, 0, 0, 0)')).toBe(0x00000000); - expect(normalizeColorBase('rgba(0, 0, 0, -0.5)')).toBe(0x00000000); - expect(normalizeColorBase('rgba(0, 0, 0, 1.0)')).toBe(0x000000ff); - expect(normalizeColorBase('rgba(0, 0, 0, 1)')).toBe(0x000000ff); - expect(normalizeColorBase('rgba(0, 0, 0, 1.5)')).toBe(0x000000ff); - expect(normalizeColorBase('rgba(100, 15, 69, 0.5)')).toBe(0x640f4580); -}); - -it('should handle hsl properly', () => { - expect(normalizeColorBase('hsl(0, 0%, 0%)')).toBe(0x000000ff); - expect(normalizeColorBase('hsl(360, 100%, 100%)')).toBe(0xffffffff); - expect(normalizeColorBase('hsl(180, 50%, 50%)')).toBe(0x40bfbfff); - expect(normalizeColorBase('hsl(540, 50%, 50%)')).toBe(0x40bfbfff); - expect(normalizeColorBase('hsl(70, 25%, 75%)')).toBe(0xcacfafff); - expect(normalizeColorBase('hsl(70, 100%, 75%)')).toBe(0xeaff80ff); - expect(normalizeColorBase('hsl(70, 110%, 75%)')).toBe(0xeaff80ff); - expect(normalizeColorBase('hsl(70, 0%, 75%)')).toBe(0xbfbfbfff); - expect(normalizeColorBase('hsl(70, -10%, 75%)')).toBe(0xbfbfbfff); -}); - -it('should handle hsla properly', () => { - expect(normalizeColorBase('hsla(0, 0%, 0%, 0)')).toBe(0x00000000); - expect(normalizeColorBase('hsla(360, 100%, 100%, 1)')).toBe(0xffffffff); - expect(normalizeColorBase('hsla(360, 100%, 100%, 0)')).toBe(0xffffff00); - expect(normalizeColorBase('hsla(180, 50%, 50%, 0.2)')).toBe(0x40bfbf33); -}); - -it('should handle named colors properly', () => { - expect(normalizeColorBase('red')).toBe(0xff0000ff); - expect(normalizeColorBase('transparent')).toBe(0x00000000); - expect(normalizeColorBase('peachpuff')).toBe(0xffdab9ff); -}); - -it('should handle number colors properly', () => { - expect(normalizeColorBase(0x00000000)).toBe(0x00000000); - expect(normalizeColorBase(0xff0000ff)).toBe(0xff0000ff); - expect(normalizeColorBase(0xffffffff)).toBe(0xffffffff); - expect(normalizeColorBase(0x01234567)).toBe(0x01234567); -}); - -it("should return the same color when it's already normalized", () => { - const normalizedColor = normalizeColorBase('red') || 0; - expect(normalizeColorBase(normalizedColor)).toBe(normalizedColor); -}); diff --git a/packages/normalize-color/__tests__/normalizeColor-test.js b/packages/normalize-color/__tests__/normalizeColor-test.js new file mode 100644 index 00000000000000..fc68e4c95ba953 --- /dev/null +++ b/packages/normalize-color/__tests__/normalizeColor-test.js @@ -0,0 +1,131 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow stroct + * @format + */ + +'use strict'; + +import normalizeColor from '..'; + +it('accepts only spec compliant colors', () => { + expect(normalizeColor('#abc')).not.toBe(null); + expect(normalizeColor('#abcd')).not.toBe(null); + 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('rgba(0, 0, 0, 1)')).not.toBe(null); + expect(normalizeColor(0x01234567 + 0.5)).toBe(null); + expect(normalizeColor(-1)).toBe(null); + expect(normalizeColor(0xffffffff + 1)).toBe(null); +}); + +it('temporarilys accept floating point values for rgb', () => { + expect(normalizeColor('rgb(1.1, 2.1, 3.1)')).toBe(0x010203ff); + expect(normalizeColor('rgba(1.1, 2.1, 3.1, 1.0)')).toBe(0x010203ff); +}); + +it('refuses non-spec compliant colors', () => { + expect(normalizeColor('#00gg00')).toBe(null); + expect(normalizeColor('rgb(1, 2, 3,)')).toBe(null); + expect(normalizeColor('rgb(1, 2, 3')).toBe(null); + + // Used to be accepted by normalizeColor + expect(normalizeColor('abc')).toBe(null); + expect(normalizeColor(' #abc ')).toBe(null); + expect(normalizeColor('##abc')).toBe(null); + 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('hsv(0, 1, 2)')).toBe(null); + // $FlowExpectedError - Intentionally malformed argument. + expect(normalizeColor({r: 10, g: 10, b: 10})).toBe(null); + expect(normalizeColor('hsl(1%, 2, 3)')).toBe(null); + expect(normalizeColor('rgb(1%, 2%, 3%)')).toBe(null); +}); + +it('handles hex6 properly', () => { + expect(normalizeColor('#000000')).toBe(0x000000ff); + expect(normalizeColor('#ffffff')).toBe(0xffffffff); + expect(normalizeColor('#ff00ff')).toBe(0xff00ffff); + expect(normalizeColor('#abcdef')).toBe(0xabcdefff); + expect(normalizeColor('#012345')).toBe(0x012345ff); +}); + +it('handles hex3 properly', () => { + expect(normalizeColor('#000')).toBe(0x000000ff); + expect(normalizeColor('#fff')).toBe(0xffffffff); + expect(normalizeColor('#f0f')).toBe(0xff00ffff); +}); + +it('handles hex8 properly', () => { + expect(normalizeColor('#00000000')).toBe(0x00000000); + expect(normalizeColor('#ffffffff')).toBe(0xffffffff); + expect(normalizeColor('#ffff00ff')).toBe(0xffff00ff); + expect(normalizeColor('#abcdef01')).toBe(0xabcdef01); + expect(normalizeColor('#01234567')).toBe(0x01234567); +}); + +it('handles rgb properly', () => { + expect(normalizeColor('rgb(0, 0, 0)')).toBe(0x000000ff); + expect(normalizeColor('rgb(-1, -2, -3)')).toBe(0x000000ff); + expect(normalizeColor('rgb(0, 0, 255)')).toBe(0x0000ffff); + expect(normalizeColor('rgb(100, 15, 69)')).toBe(0x640f45ff); + expect(normalizeColor('rgb(255, 255, 255)')).toBe(0xffffffff); + expect(normalizeColor('rgb(256, 256, 256)')).toBe(0xffffffff); +}); + +it('handles rgba properly', () => { + expect(normalizeColor('rgba(0, 0, 0, 0.0)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, 0)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, -0.5)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, 1.0)')).toBe(0x000000ff); + 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); +}); + +it('handles hsl properly', () => { + expect(normalizeColor('hsl(0, 0%, 0%)')).toBe(0x000000ff); + expect(normalizeColor('hsl(360, 100%, 100%)')).toBe(0xffffffff); + expect(normalizeColor('hsl(180, 50%, 50%)')).toBe(0x40bfbfff); + expect(normalizeColor('hsl(540, 50%, 50%)')).toBe(0x40bfbfff); + expect(normalizeColor('hsl(70, 25%, 75%)')).toBe(0xcacfafff); + expect(normalizeColor('hsl(70, 100%, 75%)')).toBe(0xeaff80ff); + expect(normalizeColor('hsl(70, 110%, 75%)')).toBe(0xeaff80ff); + expect(normalizeColor('hsl(70, 0%, 75%)')).toBe(0xbfbfbfff); + expect(normalizeColor('hsl(70, -10%, 75%)')).toBe(0xbfbfbfff); +}); + +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); +}); + +it('handles named colors properly', () => { + expect(normalizeColor('red')).toBe(0xff0000ff); + expect(normalizeColor('transparent')).toBe(0x00000000); + expect(normalizeColor('peachpuff')).toBe(0xffdab9ff); +}); + +it('handles number colors properly', () => { + expect(normalizeColor(0x00000000)).toBe(0x00000000); + expect(normalizeColor(0xff0000ff)).toBe(0xff0000ff); + expect(normalizeColor(0xffffffff)).toBe(0xffffffff); + expect(normalizeColor(0x01234567)).toBe(0x01234567); +}); + +it('returns the same color when it is already normalized', () => { + const normalizedColor = normalizeColor('red') || 0; + expect(normalizeColor(normalizedColor)).toBe(normalizedColor); +}); diff --git a/packages/normalize-color/base.js b/packages/normalize-color/base.js deleted file mode 100644 index b8e88e942065d0..00000000000000 --- a/packages/normalize-color/base.js +++ /dev/null @@ -1,387 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -/* eslint no-bitwise: 0 */ - -'use strict'; - -function normalizeColorBase(color: ?string | number): null | number { - const matchers = getMatchers(); - let match; - - if (typeof color === 'number') { - if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { - return color; - } - return null; - } - - if (typeof color !== 'string') { - return null; - } - - // Ordered based on occurrences on Facebook codebase - if ((match = matchers.hex6.exec(color))) { - return parseInt(match[1] + 'ff', 16) >>> 0; - } - - if (names.hasOwnProperty(color)) { - return names[color]; - } - - if ((match = matchers.rgb.exec(color))) { - return ( - // b - ((parse255(match[1]) << 24) | // r - // $FlowFixMe[incompatible-use] - (parse255(match[2]) << 16) | // g - // $FlowFixMe[incompatible-use] - (parse255(match[3]) << 8) | - 0x000000ff) >>> // a - 0 - ); - } - - if ((match = matchers.rgba.exec(color))) { - return ( - // b - ((parse255(match[1]) << 24) | // r - // $FlowFixMe[incompatible-use] - (parse255(match[2]) << 16) | // g - // $FlowFixMe[incompatible-use] - (parse255(match[3]) << 8) | - // $FlowFixMe[incompatible-use] - parse1(match[4])) >>> // a - 0 - ); - } - - if ((match = matchers.hex3.exec(color))) { - return ( - parseInt( - match[1] + - match[1] + // r - match[2] + - match[2] + // g - match[3] + - match[3] + // b - 'ff', // a - 16, - ) >>> 0 - ); - } - - // https://drafts.csswg.org/css-color-4/#hex-notation - if ((match = matchers.hex8.exec(color))) { - return parseInt(match[1], 16) >>> 0; - } - - if ((match = matchers.hex4.exec(color))) { - return ( - parseInt( - match[1] + - match[1] + // r - match[2] + - match[2] + // g - match[3] + - match[3] + // b - match[4] + - match[4], // a - 16, - ) >>> 0 - ); - } - - if ((match = matchers.hsl.exec(color))) { - return ( - (hslToRgb( - parse360(match[1]), // h - // $FlowFixMe[incompatible-use] - parsePercentage(match[2]), // s - // $FlowFixMe[incompatible-use] - parsePercentage(match[3]), // l - ) | - 0x000000ff) >>> // a - 0 - ); - } - - if ((match = matchers.hsla.exec(color))) { - return ( - (hslToRgb( - parse360(match[1]), // h - // $FlowFixMe[incompatible-use] - parsePercentage(match[2]), // s - // $FlowFixMe[incompatible-use] - parsePercentage(match[3]), // l - ) | - // $FlowFixMe[incompatible-use] - parse1(match[4])) >>> // a - 0 - ); - } - - return null; -} - -function hue2rgb(p: number, q: number, t: number): number { - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1 / 6) { - return p + (q - p) * 6 * t; - } - if (t < 1 / 2) { - return q; - } - if (t < 2 / 3) { - return p + (q - p) * (2 / 3 - t) * 6; - } - return p; -} - -function hslToRgb(h: number, s: number, l: number): number { - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - const r = hue2rgb(p, q, h + 1 / 3); - const g = hue2rgb(p, q, h); - const b = hue2rgb(p, q, h - 1 / 3); - - return ( - (Math.round(r * 255) << 24) | - (Math.round(g * 255) << 16) | - (Math.round(b * 255) << 8) - ); -} - -// var INTEGER = '[-+]?\\d+'; -const NUMBER = '[-+]?\\d*\\.?\\d+'; -const PERCENTAGE = NUMBER + '%'; - -function call(...args) { - return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'; -} - -let cachedMatchers; - -function getMatchers() { - if (cachedMatchers === undefined) { - cachedMatchers = { - rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)), - rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)), - hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)), - hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)), - 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})$/, - hex8: /^#([0-9a-fA-F]{8})$/, - }; - } - return cachedMatchers; -} - -function parse255(str: string): number { - const int = parseInt(str, 10); - if (int < 0) { - return 0; - } - if (int > 255) { - return 255; - } - return int; -} - -function parse360(str: string): number { - const int = parseFloat(str); - return (((int % 360) + 360) % 360) / 360; -} - -function parse1(str: string): number { - const num = parseFloat(str); - if (num < 0) { - return 0; - } - if (num > 1) { - return 255; - } - return Math.round(num * 255); -} - -function parsePercentage(str: string): number { - // parseFloat conveniently ignores the final % - const int = parseFloat(str); - if (int < 0) { - return 0; - } - if (int > 100) { - return 1; - } - return int / 100; -} - -const names = { - transparent: 0x00000000, - - // http://www.w3.org/TR/css3-color/#svg-color - aliceblue: 0xf0f8ffff, - antiquewhite: 0xfaebd7ff, - aqua: 0x00ffffff, - aquamarine: 0x7fffd4ff, - azure: 0xf0ffffff, - beige: 0xf5f5dcff, - bisque: 0xffe4c4ff, - black: 0x000000ff, - blanchedalmond: 0xffebcdff, - blue: 0x0000ffff, - blueviolet: 0x8a2be2ff, - brown: 0xa52a2aff, - burlywood: 0xdeb887ff, - burntsienna: 0xea7e5dff, - cadetblue: 0x5f9ea0ff, - chartreuse: 0x7fff00ff, - chocolate: 0xd2691eff, - coral: 0xff7f50ff, - cornflowerblue: 0x6495edff, - cornsilk: 0xfff8dcff, - crimson: 0xdc143cff, - cyan: 0x00ffffff, - darkblue: 0x00008bff, - darkcyan: 0x008b8bff, - darkgoldenrod: 0xb8860bff, - darkgray: 0xa9a9a9ff, - darkgreen: 0x006400ff, - darkgrey: 0xa9a9a9ff, - darkkhaki: 0xbdb76bff, - darkmagenta: 0x8b008bff, - darkolivegreen: 0x556b2fff, - darkorange: 0xff8c00ff, - darkorchid: 0x9932ccff, - darkred: 0x8b0000ff, - darksalmon: 0xe9967aff, - darkseagreen: 0x8fbc8fff, - darkslateblue: 0x483d8bff, - darkslategray: 0x2f4f4fff, - darkslategrey: 0x2f4f4fff, - darkturquoise: 0x00ced1ff, - darkviolet: 0x9400d3ff, - deeppink: 0xff1493ff, - deepskyblue: 0x00bfffff, - dimgray: 0x696969ff, - dimgrey: 0x696969ff, - dodgerblue: 0x1e90ffff, - firebrick: 0xb22222ff, - floralwhite: 0xfffaf0ff, - forestgreen: 0x228b22ff, - fuchsia: 0xff00ffff, - gainsboro: 0xdcdcdcff, - ghostwhite: 0xf8f8ffff, - gold: 0xffd700ff, - goldenrod: 0xdaa520ff, - gray: 0x808080ff, - green: 0x008000ff, - greenyellow: 0xadff2fff, - grey: 0x808080ff, - honeydew: 0xf0fff0ff, - hotpink: 0xff69b4ff, - indianred: 0xcd5c5cff, - indigo: 0x4b0082ff, - ivory: 0xfffff0ff, - khaki: 0xf0e68cff, - lavender: 0xe6e6faff, - lavenderblush: 0xfff0f5ff, - lawngreen: 0x7cfc00ff, - lemonchiffon: 0xfffacdff, - lightblue: 0xadd8e6ff, - lightcoral: 0xf08080ff, - lightcyan: 0xe0ffffff, - lightgoldenrodyellow: 0xfafad2ff, - lightgray: 0xd3d3d3ff, - lightgreen: 0x90ee90ff, - lightgrey: 0xd3d3d3ff, - lightpink: 0xffb6c1ff, - lightsalmon: 0xffa07aff, - lightseagreen: 0x20b2aaff, - lightskyblue: 0x87cefaff, - lightslategray: 0x778899ff, - lightslategrey: 0x778899ff, - lightsteelblue: 0xb0c4deff, - lightyellow: 0xffffe0ff, - lime: 0x00ff00ff, - limegreen: 0x32cd32ff, - linen: 0xfaf0e6ff, - magenta: 0xff00ffff, - maroon: 0x800000ff, - mediumaquamarine: 0x66cdaaff, - mediumblue: 0x0000cdff, - mediumorchid: 0xba55d3ff, - mediumpurple: 0x9370dbff, - mediumseagreen: 0x3cb371ff, - mediumslateblue: 0x7b68eeff, - mediumspringgreen: 0x00fa9aff, - mediumturquoise: 0x48d1ccff, - mediumvioletred: 0xc71585ff, - midnightblue: 0x191970ff, - mintcream: 0xf5fffaff, - mistyrose: 0xffe4e1ff, - moccasin: 0xffe4b5ff, - navajowhite: 0xffdeadff, - navy: 0x000080ff, - oldlace: 0xfdf5e6ff, - olive: 0x808000ff, - olivedrab: 0x6b8e23ff, - orange: 0xffa500ff, - orangered: 0xff4500ff, - orchid: 0xda70d6ff, - palegoldenrod: 0xeee8aaff, - palegreen: 0x98fb98ff, - paleturquoise: 0xafeeeeff, - palevioletred: 0xdb7093ff, - papayawhip: 0xffefd5ff, - peachpuff: 0xffdab9ff, - peru: 0xcd853fff, - pink: 0xffc0cbff, - plum: 0xdda0ddff, - powderblue: 0xb0e0e6ff, - purple: 0x800080ff, - rebeccapurple: 0x663399ff, - red: 0xff0000ff, - rosybrown: 0xbc8f8fff, - royalblue: 0x4169e1ff, - saddlebrown: 0x8b4513ff, - salmon: 0xfa8072ff, - sandybrown: 0xf4a460ff, - seagreen: 0x2e8b57ff, - seashell: 0xfff5eeff, - sienna: 0xa0522dff, - silver: 0xc0c0c0ff, - skyblue: 0x87ceebff, - slateblue: 0x6a5acdff, - slategray: 0x708090ff, - slategrey: 0x708090ff, - snow: 0xfffafaff, - springgreen: 0x00ff7fff, - steelblue: 0x4682b4ff, - tan: 0xd2b48cff, - teal: 0x008080ff, - thistle: 0xd8bfd8ff, - tomato: 0xff6347ff, - turquoise: 0x40e0d0ff, - violet: 0xee82eeff, - wheat: 0xf5deb3ff, - white: 0xffffffff, - whitesmoke: 0xf5f5f5ff, - yellow: 0xffff00ff, - yellowgreen: 0x9acd32ff, -}; - -module.exports = normalizeColorBase; diff --git a/packages/normalize-color/index.js b/packages/normalize-color/index.js new file mode 100644 index 00000000000000..15f50bd788f6bf --- /dev/null +++ b/packages/normalize-color/index.js @@ -0,0 +1,378 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @noflow + */ + +/* eslint no-bitwise: 0 */ + +'use strict'; + +function normalizeColor(color) { + if (typeof color === 'number') { + if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { + return color; + } + return null; + } + + if (typeof color !== 'string') { + return null; + } + + const matchers = getMatchers(); + let match; + + // Ordered based on occurrences on Facebook codebase + if ((match = matchers.hex6.exec(color))) { + return parseInt(match[1] + 'ff', 16) >>> 0; + } + + const colorFromKeyword = normalizeKeyword(color); + if (colorFromKeyword != null) { + return colorFromKeyword; + } + + if ((match = matchers.rgb.exec(color))) { + return ( + ((parse255(match[1]) << 24) | // r + (parse255(match[2]) << 16) | // g + (parse255(match[3]) << 8) | // b + 0x000000ff) >>> // a + 0 + ); + } + + if ((match = matchers.rgba.exec(color))) { + return ( + ((parse255(match[1]) << 24) | // r + (parse255(match[2]) << 16) | // g + (parse255(match[3]) << 8) | // b + parse1(match[4])) >>> // a + 0 + ); + } + + if ((match = matchers.hex3.exec(color))) { + return ( + parseInt( + match[1] + + match[1] + // r + match[2] + + match[2] + // g + match[3] + + match[3] + // b + 'ff', // a + 16, + ) >>> 0 + ); + } + + // https://drafts.csswg.org/css-color-4/#hex-notation + if ((match = matchers.hex8.exec(color))) { + return parseInt(match[1], 16) >>> 0; + } + + if ((match = matchers.hex4.exec(color))) { + return ( + parseInt( + match[1] + + match[1] + // r + match[2] + + match[2] + // g + match[3] + + match[3] + // b + match[4] + + match[4], // a + 16, + ) >>> 0 + ); + } + + if ((match = matchers.hsl.exec(color))) { + return ( + (hslToRgb( + parse360(match[1]), // h + parsePercentage(match[2]), // s + parsePercentage(match[3]), // l + ) | + 0x000000ff) >>> // a + 0 + ); + } + + if ((match = matchers.hsla.exec(color))) { + return ( + (hslToRgb( + parse360(match[1]), // h + parsePercentage(match[2]), // s + parsePercentage(match[3]), // l + ) | + parse1(match[4])) >>> // a + 0 + ); + } + + return null; +} + +function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} + +function hslToRgb(h, s, l) { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + const r = hue2rgb(p, q, h + 1 / 3); + const g = hue2rgb(p, q, h); + const b = hue2rgb(p, q, h - 1 / 3); + + return ( + (Math.round(r * 255) << 24) | + (Math.round(g * 255) << 16) | + (Math.round(b * 255) << 8) + ); +} + +const NUMBER = '[-+]?\\d*\\.?\\d+'; +const PERCENTAGE = NUMBER + '%'; + +function call(...args) { + return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'; +} + +let cachedMatchers; + +function getMatchers() { + if (cachedMatchers === undefined) { + cachedMatchers = { + rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)), + rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)), + hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)), + hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)), + 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})$/, + hex8: /^#([0-9a-fA-F]{8})$/, + }; + } + return cachedMatchers; +} + +function parse255(str) { + const int = parseInt(str, 10); + if (int < 0) { + return 0; + } + if (int > 255) { + return 255; + } + return int; +} + +function parse360(str) { + const int = parseFloat(str); + return (((int % 360) + 360) % 360) / 360; +} + +function parse1(str) { + const num = parseFloat(str); + if (num < 0) { + return 0; + } + if (num > 1) { + return 255; + } + return Math.round(num * 255); +} + +function parsePercentage(str) { + // parseFloat conveniently ignores the final % + const int = parseFloat(str); + if (int < 0) { + return 0; + } + if (int > 100) { + return 1; + } + return int / 100; +} + +function normalizeKeyword(name) { + // prettier-ignore + switch (name) { + case 'transparent': return 0x00000000; + // http://www.w3.org/TR/css3-color/#svg-color + case 'aliceblue': return 0xf0f8ffff; + case 'antiquewhite': return 0xfaebd7ff; + case 'aqua': return 0x00ffffff; + case 'aquamarine': return 0x7fffd4ff; + case 'azure': return 0xf0ffffff; + case 'beige': return 0xf5f5dcff; + case 'bisque': return 0xffe4c4ff; + case 'black': return 0x000000ff; + case 'blanchedalmond': return 0xffebcdff; + case 'blue': return 0x0000ffff; + case 'blueviolet': return 0x8a2be2ff; + case 'brown': return 0xa52a2aff; + case 'burlywood': return 0xdeb887ff; + case 'burntsienna': return 0xea7e5dff; + case 'cadetblue': return 0x5f9ea0ff; + case 'chartreuse': return 0x7fff00ff; + case 'chocolate': return 0xd2691eff; + case 'coral': return 0xff7f50ff; + case 'cornflowerblue': return 0x6495edff; + case 'cornsilk': return 0xfff8dcff; + case 'crimson': return 0xdc143cff; + case 'cyan': return 0x00ffffff; + case 'darkblue': return 0x00008bff; + case 'darkcyan': return 0x008b8bff; + case 'darkgoldenrod': return 0xb8860bff; + case 'darkgray': return 0xa9a9a9ff; + case 'darkgreen': return 0x006400ff; + case 'darkgrey': return 0xa9a9a9ff; + case 'darkkhaki': return 0xbdb76bff; + case 'darkmagenta': return 0x8b008bff; + case 'darkolivegreen': return 0x556b2fff; + case 'darkorange': return 0xff8c00ff; + case 'darkorchid': return 0x9932ccff; + case 'darkred': return 0x8b0000ff; + case 'darksalmon': return 0xe9967aff; + case 'darkseagreen': return 0x8fbc8fff; + case 'darkslateblue': return 0x483d8bff; + case 'darkslategray': return 0x2f4f4fff; + case 'darkslategrey': return 0x2f4f4fff; + case 'darkturquoise': return 0x00ced1ff; + case 'darkviolet': return 0x9400d3ff; + case 'deeppink': return 0xff1493ff; + case 'deepskyblue': return 0x00bfffff; + case 'dimgray': return 0x696969ff; + case 'dimgrey': return 0x696969ff; + case 'dodgerblue': return 0x1e90ffff; + case 'firebrick': return 0xb22222ff; + case 'floralwhite': return 0xfffaf0ff; + case 'forestgreen': return 0x228b22ff; + case 'fuchsia': return 0xff00ffff; + case 'gainsboro': return 0xdcdcdcff; + case 'ghostwhite': return 0xf8f8ffff; + case 'gold': return 0xffd700ff; + case 'goldenrod': return 0xdaa520ff; + case 'gray': return 0x808080ff; + case 'green': return 0x008000ff; + case 'greenyellow': return 0xadff2fff; + case 'grey': return 0x808080ff; + case 'honeydew': return 0xf0fff0ff; + case 'hotpink': return 0xff69b4ff; + case 'indianred': return 0xcd5c5cff; + case 'indigo': return 0x4b0082ff; + case 'ivory': return 0xfffff0ff; + case 'khaki': return 0xf0e68cff; + case 'lavender': return 0xe6e6faff; + case 'lavenderblush': return 0xfff0f5ff; + case 'lawngreen': return 0x7cfc00ff; + case 'lemonchiffon': return 0xfffacdff; + case 'lightblue': return 0xadd8e6ff; + case 'lightcoral': return 0xf08080ff; + case 'lightcyan': return 0xe0ffffff; + case 'lightgoldenrodyellow': return 0xfafad2ff; + case 'lightgray': return 0xd3d3d3ff; + case 'lightgreen': return 0x90ee90ff; + case 'lightgrey': return 0xd3d3d3ff; + case 'lightpink': return 0xffb6c1ff; + case 'lightsalmon': return 0xffa07aff; + case 'lightseagreen': return 0x20b2aaff; + case 'lightskyblue': return 0x87cefaff; + case 'lightslategray': return 0x778899ff; + case 'lightslategrey': return 0x778899ff; + case 'lightsteelblue': return 0xb0c4deff; + case 'lightyellow': return 0xffffe0ff; + case 'lime': return 0x00ff00ff; + case 'limegreen': return 0x32cd32ff; + case 'linen': return 0xfaf0e6ff; + case 'magenta': return 0xff00ffff; + case 'maroon': return 0x800000ff; + case 'mediumaquamarine': return 0x66cdaaff; + case 'mediumblue': return 0x0000cdff; + case 'mediumorchid': return 0xba55d3ff; + case 'mediumpurple': return 0x9370dbff; + case 'mediumseagreen': return 0x3cb371ff; + case 'mediumslateblue': return 0x7b68eeff; + case 'mediumspringgreen': return 0x00fa9aff; + case 'mediumturquoise': return 0x48d1ccff; + case 'mediumvioletred': return 0xc71585ff; + case 'midnightblue': return 0x191970ff; + case 'mintcream': return 0xf5fffaff; + case 'mistyrose': return 0xffe4e1ff; + case 'moccasin': return 0xffe4b5ff; + case 'navajowhite': return 0xffdeadff; + case 'navy': return 0x000080ff; + case 'oldlace': return 0xfdf5e6ff; + case 'olive': return 0x808000ff; + case 'olivedrab': return 0x6b8e23ff; + case 'orange': return 0xffa500ff; + case 'orangered': return 0xff4500ff; + case 'orchid': return 0xda70d6ff; + case 'palegoldenrod': return 0xeee8aaff; + case 'palegreen': return 0x98fb98ff; + case 'paleturquoise': return 0xafeeeeff; + case 'palevioletred': return 0xdb7093ff; + case 'papayawhip': return 0xffefd5ff; + case 'peachpuff': return 0xffdab9ff; + case 'peru': return 0xcd853fff; + case 'pink': return 0xffc0cbff; + case 'plum': return 0xdda0ddff; + case 'powderblue': return 0xb0e0e6ff; + case 'purple': return 0x800080ff; + case 'rebeccapurple': return 0x663399ff; + case 'red': return 0xff0000ff; + case 'rosybrown': return 0xbc8f8fff; + case 'royalblue': return 0x4169e1ff; + case 'saddlebrown': return 0x8b4513ff; + case 'salmon': return 0xfa8072ff; + case 'sandybrown': return 0xf4a460ff; + case 'seagreen': return 0x2e8b57ff; + case 'seashell': return 0xfff5eeff; + case 'sienna': return 0xa0522dff; + case 'silver': return 0xc0c0c0ff; + case 'skyblue': return 0x87ceebff; + case 'slateblue': return 0x6a5acdff; + case 'slategray': return 0x708090ff; + case 'slategrey': return 0x708090ff; + case 'snow': return 0xfffafaff; + case 'springgreen': return 0x00ff7fff; + case 'steelblue': return 0x4682b4ff; + case 'tan': return 0xd2b48cff; + case 'teal': return 0x008080ff; + case 'thistle': return 0xd8bfd8ff; + case 'tomato': return 0xff6347ff; + case 'turquoise': return 0x40e0d0ff; + case 'violet': return 0xee82eeff; + case 'wheat': return 0xf5deb3ff; + case 'white': return 0xffffffff; + case 'whitesmoke': return 0xf5f5f5ff; + case 'yellow': return 0xffff00ff; + case 'yellowgreen': return 0x9acd32ff; + } + return null; +} + +module.exports = normalizeColor; diff --git a/packages/normalize-color/index.js.flow b/packages/normalize-color/index.js.flow new file mode 100644 index 00000000000000..c6199ff6deeb26 --- /dev/null +++ b/packages/normalize-color/index.js.flow @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +declare module.exports: (color: ?(string | number)) => null | number; diff --git a/packages/normalize-color/package.json b/packages/normalize-color/package.json index 9ff2e8a0733d9f..7ecb89c6b89cbe 100644 --- a/packages/normalize-color/package.json +++ b/packages/normalize-color/package.json @@ -1,7 +1,7 @@ { "name": "@react-native/normalize-color", - "version": "1.0.0", - "description": "Color normalization code for React Native.", + "version": "2.0.0", + "description": "Color normalization for React Native.", "repository": { "type": "git", "url": "git@github.com:facebook/react-native.git",