Move color processing to JS
Reviewed By: @vjeux Differential Revision: D2346353
This commit is contained in:
parent
6078a4f865
commit
9a2d05d9b2
|
@ -26,12 +26,12 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
border1: {
|
||||
borderWidth: 10,
|
||||
borderColor: '#a52a2a',
|
||||
borderColor: 'brown',
|
||||
},
|
||||
borderRadius: {
|
||||
borderWidth: 10,
|
||||
borderRadius: 10,
|
||||
borderColor: '#00ffff',
|
||||
borderColor: 'cyan',
|
||||
},
|
||||
border2: {
|
||||
borderWidth: 10,
|
||||
|
|
|
@ -11,22 +11,22 @@
|
|||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
* @flow-weak
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
StyleSheet,
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
View,
|
||||
processColor,
|
||||
} = React;
|
||||
|
||||
var CIRCLE_SIZE = 80;
|
||||
var CIRCLE_COLOR = 'blue';
|
||||
var CIRCLE_HIGHLIGHT_COLOR = 'green';
|
||||
|
||||
|
||||
var PanResponderExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
|
@ -78,13 +78,13 @@ var PanResponderExample = React.createClass({
|
|||
|
||||
_highlight: function() {
|
||||
this.circle && this.circle.setNativeProps({
|
||||
backgroundColor: CIRCLE_HIGHLIGHT_COLOR
|
||||
backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)
|
||||
});
|
||||
},
|
||||
|
||||
_unHighlight: function() {
|
||||
this.circle && this.circle.setNativeProps({
|
||||
backgroundColor: CIRCLE_COLOR
|
||||
backgroundColor: processColor(CIRCLE_COLOR)
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
|
||||
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; };
|
||||
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */; };
|
||||
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; };
|
||||
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -231,7 +230,6 @@
|
|||
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = "<group>"; };
|
||||
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = "<group>"; };
|
||||
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageLoaderHelpers.h; sourceTree = "<group>"; };
|
||||
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
|
||||
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -385,7 +383,6 @@
|
|||
138D6A151B53CD440074A87E /* RCTCacheTests.m */,
|
||||
1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
|
||||
1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
|
||||
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */,
|
||||
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
|
||||
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
|
||||
|
@ -846,7 +843,6 @@
|
|||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
|
||||
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
|
||||
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */,
|
||||
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
|
||||
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@interface RCTConvert_UIColorTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConvert_UIColorTests
|
||||
|
||||
#define XCTAssertEqualColors(color1, color2) do { \
|
||||
CGFloat r1, g1, b1, a1; \
|
||||
CGFloat r2, g2, b2, a2; \
|
||||
XCTAssertTrue([(color1) getRed:&r1 green:&g1 blue:&b1 alpha:&a1] && \
|
||||
[(color2) getRed:&r2 green:&g2 blue:&b2 alpha:&a2] && \
|
||||
r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2, \
|
||||
@"rgba(%d, %d, %d, %.3f) != rgba(%d, %d, %d, %.3f)", \
|
||||
(int)(r1 * 255), (int)(g1 * 255), (int)(b1 * 255), a1, \
|
||||
(int)(r2 * 255), (int)(g2 * 255), (int)(b2 * 255), a2 \
|
||||
); \
|
||||
} while (0)
|
||||
|
||||
- (void)testHex3
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"#333"];
|
||||
UIColor *expected = [UIColor colorWithWhite:0.2 alpha:1.0];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
- (void)testHex6
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"#666"];
|
||||
UIColor *expected = [UIColor colorWithWhite:0.4 alpha:1.0];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
- (void)testRGB
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"rgb(51, 102, 153)"];
|
||||
UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:1.0];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
- (void)testRGBA
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"rgba(51, 102, 153, 0.5)"];
|
||||
UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.5];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
- (void)testHSL
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"hsl(30, 50%, 50%)"];
|
||||
UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:1.0];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
- (void)testHSLA
|
||||
{
|
||||
UIColor *color = [RCTConvert UIColor:@"hsla(30, 50%, 50%, 0.5)"];
|
||||
UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:0.5];
|
||||
XCTAssertEqualColors(color, expected);
|
||||
}
|
||||
|
||||
@end
|
|
@ -40,30 +40,7 @@ var notMultiline = {
|
|||
onSubmitEditing: true,
|
||||
};
|
||||
|
||||
var AndroidTextInputAttributes = {
|
||||
autoCapitalize: true,
|
||||
autoCorrect: true,
|
||||
autoFocus: true,
|
||||
textAlign: true,
|
||||
textAlignVertical: true,
|
||||
keyboardType: true,
|
||||
mostRecentEventCount: true,
|
||||
multiline: true,
|
||||
numberOfLines: true,
|
||||
password: true,
|
||||
placeholder: true,
|
||||
placeholderTextColor: true,
|
||||
text: true,
|
||||
testID: true,
|
||||
underlineColorAndroid: true,
|
||||
editable : true,
|
||||
};
|
||||
|
||||
var viewConfigAndroid = {
|
||||
uiViewClassName: 'AndroidTextInput',
|
||||
validAttributes: AndroidTextInputAttributes,
|
||||
};
|
||||
|
||||
var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);
|
||||
var RCTTextView = requireNativeComponent('RCTTextView', null);
|
||||
var RCTTextField = requireNativeComponent('RCTTextField', null);
|
||||
|
||||
|
@ -317,7 +294,7 @@ var TextInput = React.createClass({
|
|||
mixins: [NativeMethodsMixin, TimerMixin],
|
||||
|
||||
viewConfig: ((Platform.OS === 'ios' ? RCTTextField.viewConfig :
|
||||
(Platform.OS === 'android' ? viewConfigAndroid : {})) : Object),
|
||||
(Platform.OS === 'android' ? AndroidTextInput.viewConfig : {})) : Object),
|
||||
|
||||
isFocused: function(): boolean {
|
||||
return TextInputState.currentlyFocusedField() ===
|
||||
|
@ -578,9 +555,4 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
var AndroidTextInput = createReactNativeComponentClass({
|
||||
validAttributes: AndroidTextInputAttributes,
|
||||
uiViewClassName: 'AndroidTextInput',
|
||||
});
|
||||
|
||||
module.exports = TextInput;
|
||||
|
|
|
@ -17,7 +17,7 @@ var React = require('React');
|
|||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var ReactPropTypes = require('ReactPropTypes');
|
||||
|
||||
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
/**
|
||||
* React component that wraps the Android-only [`Toolbar` widget][0]. A Toolbar can display a logo,
|
||||
|
@ -166,9 +166,6 @@ var toolbarAttributes = {
|
|||
titleColor: true,
|
||||
};
|
||||
|
||||
var NativeToolbar = createReactNativeComponentClass({
|
||||
validAttributes: toolbarAttributes,
|
||||
uiViewClassName: 'ToolbarAndroid',
|
||||
});
|
||||
var NativeToolbar = requireNativeComponent('ToolbarAndroid', null);
|
||||
|
||||
module.exports = ToolbarAndroid;
|
||||
|
|
|
@ -21,6 +21,7 @@ var createReactNativeComponentClass = require('createReactNativeComponentClass')
|
|||
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
|
||||
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
var onlyChild = require('onlyChild');
|
||||
var processColor = require('processColor');
|
||||
|
||||
var rippleBackgroundPropType = createStrictShapeTypeChecker({
|
||||
type: React.PropTypes.oneOf(['RippleAndroid']),
|
||||
|
@ -112,7 +113,7 @@ var TouchableNativeFeedback = React.createClass({
|
|||
return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackgroundBorderless'};
|
||||
},
|
||||
Ripple: function(color, borderless) {
|
||||
return {type: 'RippleAndroid', color: color, borderless: borderless};
|
||||
return {type: 'RippleAndroid', color: processColor(color), borderless: borderless};
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -77,15 +77,34 @@ var NativeMethodsMixin = {
|
|||
break;
|
||||
}
|
||||
}
|
||||
var style = precomputeStyle(flattenStyle(nativeProps.style));
|
||||
|
||||
var validAttributes = this.viewConfig.validAttributes;
|
||||
var hasProcessedProps = false;
|
||||
var processedProps = {};
|
||||
for (var key in nativeProps) {
|
||||
var process = validAttributes[key] && validAttributes[key].process;
|
||||
if (process) {
|
||||
hasProcessedProps = true;
|
||||
processedProps[key] = process(nativeProps[key]);
|
||||
}
|
||||
}
|
||||
|
||||
var style = precomputeStyle(
|
||||
flattenStyle(processedProps.style || nativeProps.style),
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
|
||||
var props = null;
|
||||
if (hasOnlyStyle) {
|
||||
props = style;
|
||||
} else if (!style) {
|
||||
props = nativeProps;
|
||||
} else {
|
||||
props = mergeFast(nativeProps, style);
|
||||
props = nativeProps;
|
||||
if (hasProcessedProps) {
|
||||
props = mergeFast(props, processedProps);
|
||||
}
|
||||
if (style) {
|
||||
props = mergeFast(props, style);
|
||||
}
|
||||
}
|
||||
|
||||
RCTUIManager.updateView(
|
||||
|
|
|
@ -18,6 +18,7 @@ var createReactNativeComponentClass = require('createReactNativeComponentClass')
|
|||
var insetsDiffer = require('insetsDiffer');
|
||||
var pointsDiffer = require('pointsDiffer');
|
||||
var matricesDiffer = require('matricesDiffer');
|
||||
var processColor = require('processColor');
|
||||
var sizesDiffer = require('sizesDiffer');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
var warning = require('warning');
|
||||
|
@ -57,8 +58,22 @@ function requireNativeComponent(
|
|||
viewConfig.validAttributes = {};
|
||||
viewConfig.propTypes = componentInterface && componentInterface.propTypes;
|
||||
for (var key in nativeProps) {
|
||||
var useAttribute = false;
|
||||
var attribute = {};
|
||||
|
||||
var differ = TypeToDifferMap[nativeProps[key]];
|
||||
viewConfig.validAttributes[key] = differ ? {diff: differ} : true;
|
||||
if (differ) {
|
||||
attribute.diff = differ;
|
||||
useAttribute = true;
|
||||
}
|
||||
|
||||
var processor = TypeToProcessorMap[nativeProps[key]];
|
||||
if (processor) {
|
||||
attribute.process = processor;
|
||||
useAttribute = true;
|
||||
}
|
||||
|
||||
viewConfig.validAttributes[key] = useAttribute ? attribute : true;
|
||||
}
|
||||
if (__DEV__) {
|
||||
componentInterface && verifyPropTypes(
|
||||
|
@ -80,4 +95,14 @@ var TypeToDifferMap = {
|
|||
// (not yet implemented)
|
||||
};
|
||||
|
||||
var TypeToProcessorMap = {
|
||||
// iOS Types
|
||||
CGColor: processColor,
|
||||
CGColorArray: processColor,
|
||||
UIColor: processColor,
|
||||
UIColorArray: processColor,
|
||||
// Android Types
|
||||
Color: processColor,
|
||||
};
|
||||
|
||||
module.exports = requireNativeComponent;
|
||||
|
|
|
@ -158,13 +158,23 @@ ReactNativeBaseComponent.Mixin = {
|
|||
validAttributes
|
||||
);
|
||||
|
||||
for (var key in updatePayload) {
|
||||
var process = validAttributes[key] && validAttributes[key].process;
|
||||
if (process) {
|
||||
updatePayload[key] = process(updatePayload[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// The style property is a deeply nested element which includes numbers
|
||||
// to represent static objects. Most of the time, it doesn't change across
|
||||
// renders, so it's faster to spend the time checking if it is different
|
||||
// before actually doing the expensive flattening operation in order to
|
||||
// compute the diff.
|
||||
if (styleDiffer(nextProps.style, prevProps.style)) {
|
||||
var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style));
|
||||
var nextFlattenedStyle = precomputeStyle(
|
||||
flattenStyle(nextProps.style),
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
updatePayload = diffRawProperties(
|
||||
updatePayload,
|
||||
this.previousFlattenedStyle,
|
||||
|
|
|
@ -18,6 +18,7 @@ var ViewStylePropTypes = require('ViewStylePropTypes');
|
|||
|
||||
var keyMirror = require('keyMirror');
|
||||
var matricesDiffer = require('matricesDiffer');
|
||||
var processColor = require('processColor');
|
||||
var sizesDiffer = require('sizesDiffer');
|
||||
|
||||
var ReactNativeStyleAttributes = {
|
||||
|
@ -32,4 +33,16 @@ ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer };
|
|||
// Do not rely on this attribute.
|
||||
ReactNativeStyleAttributes.decomposedMatrix = 'decomposedMatrix';
|
||||
|
||||
var colorAttributes = { process: processColor };
|
||||
ReactNativeStyleAttributes.backgroundColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.borderBottomColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.borderColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.borderLeftColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.borderRightColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.borderTopColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.color = colorAttributes;
|
||||
ReactNativeStyleAttributes.shadowColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.textDecorationColor = colorAttributes;
|
||||
ReactNativeStyleAttributes.tintColor = colorAttributes;
|
||||
|
||||
module.exports = ReactNativeStyleAttributes;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
var processColor = require('processColor');
|
||||
|
||||
describe('processColor', () => {
|
||||
|
||||
describe('predefined color names', () => {
|
||||
|
||||
it('should convert red', () => {
|
||||
var colorFromString = processColor('red');
|
||||
var expectedInt = 0xFFFF0000;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert white', () => {
|
||||
var colorFromString = processColor('white');
|
||||
var expectedInt = 0xFFFFFFFF;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert black', () => {
|
||||
var colorFromString = processColor('black');
|
||||
var expectedInt = 0xFF000000;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert transparent', () => {
|
||||
var colorFromString = processColor('transparent');
|
||||
var expectedInt = 0x00000000;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RGB strings', () => {
|
||||
|
||||
it('should convert rgb(x, y, z)', () => {
|
||||
var colorFromString = processColor('rgb(10, 20, 30)');
|
||||
var expectedInt = 0xFF0A141E;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert rgb x, y, z', () => {
|
||||
var colorFromString = processColor('rgb 10, 20, 30');
|
||||
var expectedInt = 0xFF0A141E;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('RGBA strings', () => {
|
||||
|
||||
it('should convert rgba(x, y, z, a)', () => {
|
||||
var colorFromString = processColor('rgba(10, 20, 30, 0.4)');
|
||||
var expectedInt = 0x660A141E;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert rgba x, y, z, a', () => {
|
||||
var colorFromString = processColor('rgba 10, 20, 30, 0.4');
|
||||
var expectedInt = 0x660A141E;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('HSL strings', () => {
|
||||
|
||||
it('should convert hsl(x, y%, z%)', () => {
|
||||
var colorFromString = processColor('hsl(318, 69%, 55%)');
|
||||
var expectedInt = 0xFFDB3DAC;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert hsl x, y%, z%', () => {
|
||||
var colorFromString = processColor('hsl 318, 69%, 55%');
|
||||
var expectedInt = 0xFFDB3DAC;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('HSL strings', () => {
|
||||
|
||||
it('should convert hsl(x, y%, z%)', () => {
|
||||
var colorFromString = processColor('hsla(318, 69%, 55%, 0.25)');
|
||||
var expectedInt = 0x40DB3DAC;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
it('should convert hsl x, y%, z%', () => {
|
||||
var colorFromString = processColor('hsla 318, 69%, 55%, 0.25');
|
||||
var expectedInt = 0x40DB3DAC;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('hex strings', () => {
|
||||
|
||||
it('should convert #xxxxxx', () => {
|
||||
var colorFromString = processColor('#1e83c9');
|
||||
var expectedInt = 0xFF1E83C9;
|
||||
expect(colorFromString).toEqual(expectedInt);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -12,6 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var MatrixMath = require('MatrixMath');
|
||||
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
var Platform = require('Platform');
|
||||
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
|
@ -22,19 +23,57 @@ var stringifySafe = require('stringifySafe');
|
|||
* This method provides a hook where flattened styles may be precomputed or
|
||||
* otherwise prepared to become better input data for native code.
|
||||
*/
|
||||
function precomputeStyle(style: ?Object): ?Object {
|
||||
if (!style || !style.transform) {
|
||||
function precomputeStyle(style: ?Object, validAttributes: Object): ?Object {
|
||||
if (!style) {
|
||||
return style;
|
||||
}
|
||||
|
||||
var hasPreprocessKeys = false;
|
||||
for (var i = 0, keys = Object.keys(style); i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
if (_processor(key, validAttributes)) {
|
||||
hasPreprocessKeys = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPreprocessKeys && !style.transform) {
|
||||
return style;
|
||||
}
|
||||
|
||||
var newStyle = {...style};
|
||||
for (var i = 0, keys = Object.keys(style); i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var process = _processor(key, validAttributes);
|
||||
if (process) {
|
||||
newStyle[key] = process(newStyle[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (style.transform) {
|
||||
invariant(
|
||||
!style.transformMatrix,
|
||||
'transformMatrix and transform styles cannot be used on the same component'
|
||||
);
|
||||
var newStyle = _precomputeTransforms({...style});
|
||||
|
||||
newStyle = _precomputeTransforms(newStyle);
|
||||
}
|
||||
|
||||
deepFreezeAndThrowOnMutationInDev(newStyle);
|
||||
return newStyle;
|
||||
}
|
||||
|
||||
function _processor(key: string, validAttributes: Object) {
|
||||
var process = validAttributes[key] && validAttributes[key].process;
|
||||
if (!process) {
|
||||
process =
|
||||
ReactNativeStyleAttributes[key] &&
|
||||
ReactNativeStyleAttributes[key].process;
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transform matrix based on the provided transforms, and use that
|
||||
* within the style object instead.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule processColor
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var tinycolor = require('tinycolor2');
|
||||
|
||||
function processColor(color) {
|
||||
if (!color || typeof color === 'number') {
|
||||
return color;
|
||||
} else if (color instanceof Array) {
|
||||
return color.map(processColor);
|
||||
} else {
|
||||
var hexString = tinycolor(color).toHex8();
|
||||
return parseInt(hexString, 16);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = processColor;
|
|
@ -78,6 +78,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
|
||||
NativeModules: require('NativeModules'),
|
||||
Platform: require('Platform'),
|
||||
processColor: require('processColor'),
|
||||
requireNativeComponent: require('requireNativeComponent'),
|
||||
|
||||
// Prop Types
|
||||
|
|
|
@ -379,292 +379,21 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
|||
|
||||
+ (UIColor *)UIColor:(id)json
|
||||
{
|
||||
// Check color cache
|
||||
static RCTCache *colorCache = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorCache = [RCTCache new];
|
||||
colorCache.countLimit = 128;
|
||||
});
|
||||
UIColor *color = colorCache[json];
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
|
||||
// Check named colors
|
||||
static NSDictionary *namedColors = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
namedColors = @{
|
||||
|
||||
// CSS colors
|
||||
@"aliceblue": @"#f0f8ff",
|
||||
@"antiquewhite": @"#faebd7",
|
||||
@"aqua": @"#00ffff",
|
||||
@"aquamarine": @"#7fffd4",
|
||||
@"azure": @"#f0ffff",
|
||||
@"beige": @"#f5f5dc",
|
||||
@"bisque": @"#ffe4c4",
|
||||
@"black": @"#000000",
|
||||
@"blanchedalmond": @"#ffebcd",
|
||||
@"blue": @"#0000ff",
|
||||
@"blueviolet": @"#8a2be2",
|
||||
@"brown": @"#a52a2a",
|
||||
@"burlywood": @"#deb887",
|
||||
@"cadetblue": @"#5f9ea0",
|
||||
@"chartreuse": @"#7fff00",
|
||||
@"chocolate": @"#d2691e",
|
||||
@"coral": @"#ff7f50",
|
||||
@"cornflowerblue": @"#6495ed",
|
||||
@"cornsilk": @"#fff8dc",
|
||||
@"crimson": @"#dc143c",
|
||||
@"cyan": @"#00ffff",
|
||||
@"darkblue": @"#00008b",
|
||||
@"darkcyan": @"#008b8b",
|
||||
@"darkgoldenrod": @"#b8860b",
|
||||
@"darkgray": @"#a9a9a9",
|
||||
@"darkgrey": @"#a9a9a9",
|
||||
@"darkgreen": @"#006400",
|
||||
@"darkkhaki": @"#bdb76b",
|
||||
@"darkmagenta": @"#8b008b",
|
||||
@"darkolivegreen": @"#556b2f",
|
||||
@"darkorange": @"#ff8c00",
|
||||
@"darkorchid": @"#9932cc",
|
||||
@"darkred": @"#8b0000",
|
||||
@"darksalmon": @"#e9967a",
|
||||
@"darkseagreen": @"#8fbc8f",
|
||||
@"darkslateblue": @"#483d8b",
|
||||
@"darkslategray": @"#2f4f4f",
|
||||
@"darkslategrey": @"#2f4f4f",
|
||||
@"darkturquoise": @"#00ced1",
|
||||
@"darkviolet": @"#9400d3",
|
||||
@"deeppink": @"#ff1493",
|
||||
@"deepskyblue": @"#00bfff",
|
||||
@"dimgray": @"#696969",
|
||||
@"dimgrey": @"#696969",
|
||||
@"dodgerblue": @"#1e90ff",
|
||||
@"firebrick": @"#b22222",
|
||||
@"floralwhite": @"#fffaf0",
|
||||
@"forestgreen": @"#228b22",
|
||||
@"fuchsia": @"#ff00ff",
|
||||
@"gainsboro": @"#dcdcdc",
|
||||
@"ghostwhite": @"#f8f8ff",
|
||||
@"gold": @"#ffd700",
|
||||
@"goldenrod": @"#daa520",
|
||||
@"gray": @"#808080",
|
||||
@"grey": @"#808080",
|
||||
@"green": @"#008000",
|
||||
@"greenyellow": @"#adff2f",
|
||||
@"honeydew": @"#f0fff0",
|
||||
@"hotpink": @"#ff69b4",
|
||||
@"indianred": @"#cd5c5c",
|
||||
@"indigo": @"#4b0082",
|
||||
@"ivory": @"#fffff0",
|
||||
@"khaki": @"#f0e68c",
|
||||
@"lavender": @"#e6e6fa",
|
||||
@"lavenderblush": @"#fff0f5",
|
||||
@"lawngreen": @"#7cfc00",
|
||||
@"lemonchiffon": @"#fffacd",
|
||||
@"lightblue": @"#add8e6",
|
||||
@"lightcoral": @"#f08080",
|
||||
@"lightcyan": @"#e0ffff",
|
||||
@"lightgoldenrodyellow": @"#fafad2",
|
||||
@"lightgray": @"#d3d3d3",
|
||||
@"lightgrey": @"#d3d3d3",
|
||||
@"lightgreen": @"#90ee90",
|
||||
@"lightpink": @"#ffb6c1",
|
||||
@"lightsalmon": @"#ffa07a",
|
||||
@"lightseagreen": @"#20b2aa",
|
||||
@"lightskyblue": @"#87cefa",
|
||||
@"lightslategray": @"#778899",
|
||||
@"lightslategrey": @"#778899",
|
||||
@"lightsteelblue": @"#b0c4de",
|
||||
@"lightyellow": @"#ffffe0",
|
||||
@"lime": @"#00ff00",
|
||||
@"limegreen": @"#32cd32",
|
||||
@"linen": @"#faf0e6",
|
||||
@"magenta": @"#ff00ff",
|
||||
@"maroon": @"#800000",
|
||||
@"mediumaquamarine": @"#66cdaa",
|
||||
@"mediumblue": @"#0000cd",
|
||||
@"mediumorchid": @"#ba55d3",
|
||||
@"mediumpurple": @"#9370db",
|
||||
@"mediumseagreen": @"#3cb371",
|
||||
@"mediumslateblue": @"#7b68ee",
|
||||
@"mediumspringgreen": @"#00fa9a",
|
||||
@"mediumturquoise": @"#48d1cc",
|
||||
@"mediumvioletred": @"#c71585",
|
||||
@"midnightblue": @"#191970",
|
||||
@"mintcream": @"#f5fffa",
|
||||
@"mistyrose": @"#ffe4e1",
|
||||
@"moccasin": @"#ffe4b5",
|
||||
@"navajowhite": @"#ffdead",
|
||||
@"navy": @"#000080",
|
||||
@"oldlace": @"#fdf5e6",
|
||||
@"olive": @"#808000",
|
||||
@"olivedrab": @"#6b8e23",
|
||||
@"orange": @"#ffa500",
|
||||
@"orangered": @"#ff4500",
|
||||
@"orchid": @"#da70d6",
|
||||
@"palegoldenrod": @"#eee8aa",
|
||||
@"palegreen": @"#98fb98",
|
||||
@"paleturquoise": @"#afeeee",
|
||||
@"palevioletred": @"#db7093",
|
||||
@"papayawhip": @"#ffefd5",
|
||||
@"peachpuff": @"#ffdab9",
|
||||
@"peru": @"#cd853f",
|
||||
@"pink": @"#ffc0cb",
|
||||
@"plum": @"#dda0dd",
|
||||
@"powderblue": @"#b0e0e6",
|
||||
@"purple": @"#800080",
|
||||
@"rebeccapurple": @"#663399",
|
||||
@"red": @"#ff0000",
|
||||
@"rosybrown": @"#bc8f8f",
|
||||
@"royalblue": @"#4169e1",
|
||||
@"saddlebrown": @"#8b4513",
|
||||
@"salmon": @"#fa8072",
|
||||
@"sandybrown": @"#f4a460",
|
||||
@"seagreen": @"#2e8b57",
|
||||
@"seashell": @"#fff5ee",
|
||||
@"sienna": @"#a0522d",
|
||||
@"silver": @"#c0c0c0",
|
||||
@"skyblue": @"#87ceeb",
|
||||
@"slateblue": @"#6a5acd",
|
||||
@"slategray": @"#708090",
|
||||
@"slategrey": @"#708090",
|
||||
@"snow": @"#fffafa",
|
||||
@"springgreen": @"#00ff7f",
|
||||
@"steelblue": @"#4682b4",
|
||||
@"tan": @"#d2b48c",
|
||||
@"teal": @"#008080",
|
||||
@"thistle": @"#d8bfd8",
|
||||
@"tomato": @"#ff6347",
|
||||
@"turquoise": @"#40e0d0",
|
||||
@"violet": @"#ee82ee",
|
||||
@"wheat": @"#f5deb3",
|
||||
@"white": @"#ffffff",
|
||||
@"whitesmoke": @"#f5f5f5",
|
||||
@"yellow": @"#ffff00",
|
||||
@"yellowgreen": @"#9acd32",
|
||||
|
||||
// Nonstandard color extensions
|
||||
@"transparent": @"rgba(0,0,0,0)",
|
||||
};
|
||||
});
|
||||
NSString *colorString = namedColors[json];
|
||||
if (!colorString) {
|
||||
colorString = json;
|
||||
}
|
||||
|
||||
// Parse color
|
||||
enum {
|
||||
MODE_RGB = 0,
|
||||
MODE_HSB = 1,
|
||||
};
|
||||
struct {
|
||||
union {
|
||||
struct {
|
||||
double r, g, b;
|
||||
} rgb;
|
||||
struct {
|
||||
double h, s, b;
|
||||
} hsb;
|
||||
};
|
||||
double a;
|
||||
unsigned int mode: 1;
|
||||
} components = {
|
||||
.a = 1.0,
|
||||
.mode = MODE_RGB,
|
||||
};
|
||||
|
||||
if ([colorString hasPrefix:@"#"]) {
|
||||
uint32_t redInt = 0, greenInt = 0, blueInt = 0;
|
||||
if (colorString.length == 4) { // 3 digit hex
|
||||
sscanf(colorString.UTF8String, "#%01x%01x%01x", &redInt, &greenInt, &blueInt);
|
||||
// expand to 6 digit hex
|
||||
components.rgb.r = redInt / 15.0;
|
||||
components.rgb.g = greenInt / 15.0;
|
||||
components.rgb.b = blueInt / 15.0;
|
||||
} else if (colorString.length == 7) { // 6 digit hex
|
||||
sscanf(colorString.UTF8String, "#%02x%02x%02x", &redInt, &greenInt, &blueInt);
|
||||
components.rgb.r = redInt / 255.0;
|
||||
components.rgb.g = greenInt / 255.0;
|
||||
components.rgb.b = blueInt / 255.0;
|
||||
if ([json isKindOfClass:[NSArray class]]) {
|
||||
NSArray *components = [self NSNumberArray:json];
|
||||
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
|
||||
return [UIColor colorWithRed:[self CGFloat:components[0]]
|
||||
green:[self CGFloat:components[1]]
|
||||
blue:[self CGFloat:components[2]]
|
||||
alpha:alpha];
|
||||
} else {
|
||||
RCTLogError(@"Invalid hex color %@. Hex colors should be 3 or 6 digits long.", colorString);
|
||||
components.a = -1;
|
||||
NSUInteger argb = [self NSUInteger:json];
|
||||
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
|
||||
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
|
||||
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
|
||||
CGFloat b = (argb & 0xFF) / 255.0;
|
||||
return [UIColor colorWithRed:r green:g blue:b alpha:a];
|
||||
}
|
||||
} else if (4 == sscanf(colorString.UTF8String, "rgba(%lf,%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b, &components.a) ||
|
||||
3 == sscanf(colorString.UTF8String, "rgb(%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b)) {
|
||||
components.rgb.r /= 255.0;
|
||||
components.rgb.g /= 255.0;
|
||||
components.rgb.b /= 255.0;
|
||||
} else if (4 == sscanf(colorString.UTF8String, "hsla(%lf,%lf%%,%lf%%,%lf)", &components.hsb.h, &components.hsb.s, &components.hsb.b, &components.a) ||
|
||||
3 == sscanf(colorString.UTF8String, "hsl(%lf,%lf%%,%lf%%)", &components.hsb.h, &components.hsb.s, &components.hsb.b)) {
|
||||
components.hsb.h /= 360.0;
|
||||
components.hsb.s /= 100.0;
|
||||
components.hsb.b /= 100.0;
|
||||
components.mode = MODE_HSB;
|
||||
} else {
|
||||
RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb or a valid CSS color name.", colorString);
|
||||
components.a = -1;
|
||||
}
|
||||
if (components.a < 0) {
|
||||
RCTLogError(@"Invalid color string '%@'", colorString);
|
||||
} else {
|
||||
if (components.mode == MODE_RGB) {
|
||||
color = [UIColor colorWithRed:components.rgb.r green:components.rgb.g blue:components.rgb.b alpha:components.a];
|
||||
} else {
|
||||
color = [UIColor colorWithHue:components.hsb.h saturation:components.hsb.s brightness:components.hsb.b alpha:components.a];
|
||||
}
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSArray class]]) {
|
||||
|
||||
if ([json count] < 3 || [json count] > 4) {
|
||||
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
} else {
|
||||
|
||||
// Color array
|
||||
color = [UIColor colorWithRed:[self CGFloat:json[0]]
|
||||
green:[self CGFloat:json[1]]
|
||||
blue:[self CGFloat:json[2]]
|
||||
alpha:[json count] > 3 ? [self CGFloat:json[3]] : 1];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
// Color dictionary
|
||||
if (json[@"r"]) {
|
||||
color = [UIColor colorWithRed:[self CGFloat:json[@"r"]]
|
||||
green:[self CGFloat:json[@"g"]]
|
||||
blue:[self CGFloat:json[@"b"]]
|
||||
alpha:[self CGFloat:json[@"a"] ?: @1]];
|
||||
} else if (json[@"h"]) {
|
||||
color = [UIColor colorWithHue:[self CGFloat:json[@"h"]]
|
||||
saturation:[self CGFloat:json[@"s"]]
|
||||
brightness:[self CGFloat:json[@"b"]]
|
||||
alpha:[self CGFloat:json[@"a"] ?: @1]];
|
||||
} else {
|
||||
RCTLogError(@"Expected dictionary with keys {r,g,b} or {h,s,b}, got: %@", [json allKeys]);
|
||||
}
|
||||
|
||||
} else if (json) {
|
||||
RCTLogConvertError(json, @"a color");
|
||||
}
|
||||
|
||||
// Default color
|
||||
if (!color) {
|
||||
color = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
// Cache and return
|
||||
if (json) {
|
||||
colorCache[json] = color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
"semver": "^5.0.1",
|
||||
"source-map": "^0.4.4",
|
||||
"stacktrace-parser": "^0.1.3",
|
||||
"tinycolor2": "^1.1.2",
|
||||
"uglify-js": "^2.4.24",
|
||||
"underscore": "^1.8.3",
|
||||
"wordwrap": "^1.0.0",
|
||||
|
|
Loading…
Reference in New Issue