Move color processing to JS

Reviewed By: @vjeux

Differential Revision: D2346353
This commit is contained in:
Alexsander Akers
2015-09-17 08:36:08 -07:00
committed by facebook-github-bot-7
parent 6078a4f865
commit 9a2d05d9b2
17 changed files with 292 additions and 424 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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};
},
},

View File

@@ -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(

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);
});
});
});

View File

@@ -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;
}
invariant(
!style.transformMatrix,
'transformMatrix and transform styles cannot be used on the same component'
);
var newStyle = _precomputeTransforms({...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'
);
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.

View File

@@ -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;

View File

@@ -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