mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 22:42:37 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03769f7d45 | ||
|
|
eb43a8f3e7 | ||
|
|
cdf13b880d | ||
|
|
47fad1ef58 | ||
|
|
5f69c8e8b8 | ||
|
|
21550db5f2 | ||
|
|
1cae5d55a1 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.91",
|
||||
"version": "0.0.93",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
|
||||
@@ -6,13 +6,12 @@ import createReactDOMStyle from './createReactDOMStyle';
|
||||
import flattenArray from '../../modules/flattenArray';
|
||||
import flattenStyle from './flattenStyle';
|
||||
import I18nManager from '../I18nManager';
|
||||
import i18nStyle from './i18nStyle';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import { prefixInlineStyles } from '../../modules/prefixStyles';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import StyleManager from './StyleManager';
|
||||
|
||||
const vendorPrefixPattern = /^(webkit|moz|ms)/;
|
||||
|
||||
const createCacheKey = id => {
|
||||
const prefix = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
return `${prefix}-${id}`;
|
||||
@@ -36,7 +35,7 @@ class StyleRegistry {
|
||||
register(flatStyle) {
|
||||
const id = ReactNativePropRegistry.register(flatStyle);
|
||||
const key = createCacheKey(id);
|
||||
const style = createReactDOMStyle(flatStyle);
|
||||
const style = createReactDOMStyle(i18nStyle(flatStyle));
|
||||
const classList = mapKeyValue(style, (prop, value) => {
|
||||
if (value != null) {
|
||||
return this.styleManager.setDeclaration(prop, value);
|
||||
@@ -50,7 +49,7 @@ class StyleRegistry {
|
||||
/**
|
||||
* Resolves a React Native style object to DOM attributes
|
||||
*/
|
||||
resolve(reactNativeStyle) {
|
||||
resolve(reactNativeStyle, options) {
|
||||
if (!reactNativeStyle) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -58,12 +57,12 @@ class StyleRegistry {
|
||||
// fast and cachable
|
||||
if (typeof reactNativeStyle === 'number') {
|
||||
const key = createCacheKey(reactNativeStyle);
|
||||
return this._resolveStyleIfNeeded(key, reactNativeStyle);
|
||||
return this._resolveStyleIfNeeded(reactNativeStyle, { key, ...options });
|
||||
}
|
||||
|
||||
// resolve a plain RN style object
|
||||
if (!Array.isArray(reactNativeStyle)) {
|
||||
return this._resolveStyle(reactNativeStyle);
|
||||
return this._resolveStyle(reactNativeStyle, options);
|
||||
}
|
||||
|
||||
// flatten the style array
|
||||
@@ -78,7 +77,7 @@ class StyleRegistry {
|
||||
}
|
||||
}
|
||||
const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
|
||||
return this._resolveStyleIfNeeded(key, flatArray);
|
||||
return this._resolveStyleIfNeeded(flatArray, { key, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,10 +87,12 @@ class StyleRegistry {
|
||||
* To determine the next style, some of the existing DOM state must be
|
||||
* converted back into React Native styles.
|
||||
*/
|
||||
resolveStateful(rnStyleNext, { classList: domClassList, style: domStyle }) {
|
||||
resolveStateful(rnStyleNext, domStyleProps, options) {
|
||||
const { classList: rdomClassList, style: rdomStyle } = domStyleProps;
|
||||
|
||||
// Convert the DOM classList back into a React Native form
|
||||
// Preserves unrecognized class names.
|
||||
const { classList: rnClassList, style: rnStyle } = domClassList.reduce(
|
||||
const { classList: rnClassList, style: rnStyle } = rdomClassList.reduce(
|
||||
(styleProps, className) => {
|
||||
const { prop, value } = this.styleManager.getDeclaration(className);
|
||||
if (prop) {
|
||||
@@ -104,24 +105,12 @@ class StyleRegistry {
|
||||
{ classList: [], style: {} }
|
||||
);
|
||||
|
||||
// DOM style may include vendor prefixes and properties set by other libraries.
|
||||
// Preserve it but transform back into React DOM style.
|
||||
const rdomStyle = Object.keys(domStyle).reduce((acc, styleName) => {
|
||||
const value = domStyle[styleName];
|
||||
if (value !== '') {
|
||||
const reactStyleName = vendorPrefixPattern.test(styleName)
|
||||
? styleName.charAt(0).toUpperCase() + styleName.slice(1)
|
||||
: styleName;
|
||||
acc[reactStyleName] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Create next DOM style props from current and next RN styles
|
||||
const { classList: rdomClassListNext, style: rdomStyleNext } = this.resolve([
|
||||
rnStyle,
|
||||
rnStyleNext
|
||||
]);
|
||||
const { classList: rdomClassListNext, style: rdomStyleNext } = this.resolve(
|
||||
[rnStyle, rnStyleNext],
|
||||
options
|
||||
);
|
||||
|
||||
// Next class names take priority over current inline styles
|
||||
const style = { ...rdomStyle };
|
||||
rdomClassListNext.forEach(className => {
|
||||
@@ -130,8 +119,10 @@ class StyleRegistry {
|
||||
style[prop] = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Next inline styles take priority over current inline styles
|
||||
Object.assign(style, rdomStyleNext);
|
||||
|
||||
// Add the current class names not managed by React Native
|
||||
const className = classListToString(rdomClassListNext.concat(rnClassList));
|
||||
|
||||
@@ -141,8 +132,9 @@ class StyleRegistry {
|
||||
/**
|
||||
* Resolves a React Native style object
|
||||
*/
|
||||
_resolveStyle(reactNativeStyle) {
|
||||
const domStyle = createReactDOMStyle(flattenStyle(reactNativeStyle));
|
||||
_resolveStyle(reactNativeStyle, options) {
|
||||
const flatStyle = flattenStyle(reactNativeStyle);
|
||||
const domStyle = createReactDOMStyle(options.i18n === false ? flatStyle : i18nStyle(flatStyle));
|
||||
|
||||
const props = Object.keys(domStyle).reduce(
|
||||
(props, styleProp) => {
|
||||
@@ -174,15 +166,15 @@ class StyleRegistry {
|
||||
/**
|
||||
* Caching layer over 'resolveStyle'
|
||||
*/
|
||||
_resolveStyleIfNeeded(key, style) {
|
||||
_resolveStyleIfNeeded(style, { key, ...rest }) {
|
||||
if (key) {
|
||||
if (!this.cache[key]) {
|
||||
// slow: convert style object to props and cache
|
||||
this.cache[key] = this._resolveStyle(style);
|
||||
this.cache[key] = this._resolveStyle(style, rest);
|
||||
}
|
||||
return this.cache[key];
|
||||
}
|
||||
return this._resolveStyle(style);
|
||||
return this._resolveStyle(style, rest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import I18nManager from '../../I18nManager';
|
||||
import StyleRegistry from '../StyleRegistry';
|
||||
|
||||
let styleRegistry;
|
||||
@@ -46,6 +47,16 @@ describe('apis/StyleSheet/StyleRegistry', () => {
|
||||
testResolve(a, b, c);
|
||||
});
|
||||
|
||||
test('with register before RTL, resolves to className', () => {
|
||||
const a = styleRegistry.register({ left: '12.34%' });
|
||||
const b = styleRegistry.register({ textAlign: 'left' });
|
||||
const c = styleRegistry.register({ marginLeft: 10 });
|
||||
I18nManager.forceRTL(true);
|
||||
const resolved = styleRegistry.resolve([a, b, c]);
|
||||
I18nManager.forceRTL(false);
|
||||
expect(resolved).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with register, resolves to mixed', () => {
|
||||
const a = styleA;
|
||||
const b = styleRegistry.register(styleB);
|
||||
@@ -82,7 +93,7 @@ describe('apis/StyleSheet/StyleRegistry', () => {
|
||||
// note: this also checks for correctly uppercasing the first letter of DOM vendor prefixes
|
||||
const domStyleProps = {
|
||||
classList: [],
|
||||
style: { opacity: 0.5, webkitTransform: 'scale(1)', transform: 'scale(1)' }
|
||||
style: { opacity: 0.5, WebkitTransform: 'scale(1)', transform: 'scale(1)' }
|
||||
};
|
||||
const domStyleNextProps = styleRegistry.resolveStateful(
|
||||
{ opacity: 1, transform: [{ scale: 2 }] },
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apis/StyleSheet/StyleRegistry resolve with register before RTL, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [],
|
||||
"className": "",
|
||||
"style": Object {
|
||||
"marginRight": "10px",
|
||||
"right": "12.34%",
|
||||
"textAlign": "right",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/StyleRegistry resolve with register, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = `
|
||||
exports[`apis/StyleSheet/createReactDOMStyle shortform -> longform 1`] = `
|
||||
Object {
|
||||
"borderBottomColor": "white",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomWidth": "1px",
|
||||
"borderLeftWidth": "1px",
|
||||
"borderRightWidth": "1px",
|
||||
"borderTopWidth": "1px",
|
||||
"borderWidthLeft": "2px",
|
||||
"borderWidthRight": "3px",
|
||||
"boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px red",
|
||||
"display": "flex",
|
||||
"flexShrink": 0,
|
||||
"marginBottom": "0px",
|
||||
"marginTop": "0px",
|
||||
"opacity": 0,
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopWidth": "0px",
|
||||
"boxSizing": "border-box",
|
||||
"marginBottom": "25px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "10px",
|
||||
"marginTop": "50px",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apis/StyleSheet/expandStyle shortform -> longform 1`] = `
|
||||
Object {
|
||||
"borderBottomColor": "white",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomWidth": "1px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopWidth": "0px",
|
||||
"boxSizing": "border-box",
|
||||
"marginBottom": "25px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "10px",
|
||||
"marginTop": "50px",
|
||||
}
|
||||
`;
|
||||
@@ -16,13 +16,148 @@ const reactNativeStyle = {
|
||||
};
|
||||
|
||||
describe('apis/StyleSheet/createReactDOMStyle', () => {
|
||||
test('converts ReactNative style to ReactDOM style', () => {
|
||||
expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('noop on DOM styles', () => {
|
||||
const firstStyle = createReactDOMStyle(reactNativeStyle);
|
||||
const secondStyle = createReactDOMStyle(firstStyle);
|
||||
expect(firstStyle).toEqual(secondStyle);
|
||||
});
|
||||
|
||||
test('flex', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0
|
||||
});
|
||||
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 10 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 10,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
|
||||
expect(createReactDOMStyle({ display: 'flex', flexShrink: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 1
|
||||
});
|
||||
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 2,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
});
|
||||
|
||||
test('shortform -> longform', () => {
|
||||
const style = {
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
borderBottomWidth: 1,
|
||||
borderWidth: 0,
|
||||
marginTop: 50,
|
||||
marginVertical: 25,
|
||||
margin: 10
|
||||
};
|
||||
|
||||
expect(createReactDOMStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('shadow styles', () => {
|
||||
test('shadowColor only', () => {
|
||||
const style = { shadowColor: 'red' };
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
boxShadow: '0px 0px 0px red'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowColor and shadowOpacity only', () => {
|
||||
expect(createReactDOMStyle({ shadowColor: 'red', shadowOpacity: 0.5 })).toEqual({
|
||||
boxShadow: '0px 0px 0px rgba(255,0,0,0.5)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowOffset only', () => {
|
||||
expect(createReactDOMStyle({ shadowOffset: { width: 1, height: 2 } })).toEqual({});
|
||||
});
|
||||
|
||||
test('shadowRadius only', () => {
|
||||
expect(createReactDOMStyle({ shadowRadius: 5 })).toEqual({});
|
||||
});
|
||||
|
||||
test('shadowOffset, shadowRadius, shadowColor', () => {
|
||||
expect(
|
||||
createReactDOMStyle({
|
||||
shadowColor: 'rgba(50,60,70,0.5)',
|
||||
shadowOffset: { width: 1, height: 2 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 3
|
||||
})
|
||||
).toEqual({
|
||||
boxShadow: '1px 2px 3px rgba(50,60,70,0.25)'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('textAlignVertical', () => {
|
||||
expect(
|
||||
createReactDOMStyle({
|
||||
textAlignVertical: 'center'
|
||||
})
|
||||
).toEqual({
|
||||
verticalAlign: 'middle'
|
||||
});
|
||||
});
|
||||
|
||||
test('textShadowOffset', () => {
|
||||
expect(
|
||||
createReactDOMStyle({
|
||||
textShadowColor: 'red',
|
||||
textShadowOffset: { width: 1, height: 2 },
|
||||
textShadowRadius: 5
|
||||
})
|
||||
).toEqual({
|
||||
textShadow: '1px 2px 5px red'
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
// passthrough if transform value is ever a string
|
||||
test('string', () => {
|
||||
const transform = 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)';
|
||||
const style = { transform };
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({ transform });
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const style = {
|
||||
transform: [{ perspective: 50 }, { scaleX: 20 }, { translateX: 20 }, { rotate: '20deg' }]
|
||||
};
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'
|
||||
});
|
||||
});
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const style = { transformMatrix: [1, 2, 3, 4, 5, 6] };
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
transform: 'matrix3d(1,2,3,4,5,6)'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import expandStyle from '../expandStyle';
|
||||
|
||||
describe('apis/StyleSheet/expandStyle', () => {
|
||||
test('shortform -> longform', () => {
|
||||
const style = {
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
borderBottomWidth: 1,
|
||||
borderWidth: 0,
|
||||
marginTop: 50,
|
||||
marginVertical: 25,
|
||||
margin: 10
|
||||
};
|
||||
|
||||
expect(expandStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('textAlignVertical', () => {
|
||||
const initial = {
|
||||
textAlignVertical: 'center'
|
||||
};
|
||||
|
||||
const expected = {
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
|
||||
expect(expandStyle(initial)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('flex', () => {
|
||||
const value = 10;
|
||||
|
||||
const initial = {
|
||||
flex: value
|
||||
};
|
||||
|
||||
const expected = {
|
||||
flexGrow: value,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
};
|
||||
|
||||
expect(expandStyle(initial)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('flex', () => {
|
||||
expect(expandStyle({ display: 'flex' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0
|
||||
});
|
||||
|
||||
expect(expandStyle({ display: 'flex', flex: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
|
||||
expect(expandStyle({ display: 'flex', flex: 10 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 10,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
|
||||
expect(expandStyle({ display: 'flex', flexShrink: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 1
|
||||
});
|
||||
|
||||
expect(expandStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 2,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveBoxShadow from '../resolveBoxShadow';
|
||||
|
||||
describe('apis/StyleSheet/resolveBoxShadow', () => {
|
||||
test('shadowColor only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowColor: 'red' };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '0px 0px 0px red'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowColor and shadowOpacity only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowColor: 'red', shadowOpacity: 0.5 };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '0px 0px 0px rgba(255,0,0,0.5)'
|
||||
});
|
||||
});
|
||||
|
||||
test('shadowOffset only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowOffset: { width: 1, height: 2 } };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({});
|
||||
});
|
||||
|
||||
test('shadowRadius only', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { shadowRadius: 5 };
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({});
|
||||
});
|
||||
|
||||
test('shadowOffset, shadowRadius, shadowColor', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
shadowColor: 'rgba(50,60,70,0.5)',
|
||||
shadowOffset: { width: 1, height: 2 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 3
|
||||
};
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
boxShadow: '1px 2px 3px rgba(50,60,70,0.25)'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveTextShadow from '../resolveTextShadow';
|
||||
|
||||
describe('apis/StyleSheet/resolveTextShadow', () => {
|
||||
test('textShadowOffset', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
textShadowColor: 'red',
|
||||
textShadowOffset: { width: 1, height: 2 },
|
||||
textShadowRadius: 5
|
||||
};
|
||||
resolveTextShadow(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
textShadow: '1px 2px 5px red'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveTransform from '../resolveTransform';
|
||||
|
||||
describe('apis/StyleSheet/resolveTransform', () => {
|
||||
// passthrough if transform value is ever a string
|
||||
test('transform string', () => {
|
||||
const resolvedStyle = {};
|
||||
const transform = 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)';
|
||||
|
||||
const style = { transform };
|
||||
resolveTransform(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({ transform });
|
||||
});
|
||||
|
||||
test('transform array', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = {
|
||||
transform: [{ perspective: 50 }, { scaleX: 20 }, { translateX: 20 }, { rotate: '20deg' }]
|
||||
};
|
||||
resolveTransform(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'
|
||||
});
|
||||
});
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const resolvedStyle = {};
|
||||
const style = { transformMatrix: [1, 2, 3, 4, 5, 6] };
|
||||
resolveTransform(resolvedStyle, style);
|
||||
|
||||
expect(resolvedStyle).toEqual({
|
||||
transform: 'matrix3d(1,2,3,4,5,6)'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,226 @@
|
||||
import expandStyle from './expandStyle';
|
||||
import i18nStyle from './i18nStyle';
|
||||
/**
|
||||
* The browser implements the CSS cascade, where the order of properties is a
|
||||
* factor in determining which styles to paint. React Native is different in
|
||||
* giving precedence to the more specific styles. For example, the value of
|
||||
* `paddingTop` takes precedence over that of `padding`.
|
||||
*
|
||||
* This module creates mutally exclusive style declarations by expanding all of
|
||||
* React Native's supported shortform properties (e.g. `padding`) to their
|
||||
* longfrom equivalents.
|
||||
*/
|
||||
|
||||
const createReactDOMStyle = flattenedReactNativeStyle =>
|
||||
expandStyle(i18nStyle(flattenedReactNativeStyle));
|
||||
import normalizeValue from './normalizeValue';
|
||||
import processColor from '../../modules/processColor';
|
||||
|
||||
const emptyObject = {};
|
||||
const styleShortFormProperties = {
|
||||
borderColor: ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
||||
borderRadius: [
|
||||
'borderTopLeftRadius',
|
||||
'borderTopRightRadius',
|
||||
'borderBottomRightRadius',
|
||||
'borderBottomLeftRadius'
|
||||
],
|
||||
borderStyle: ['borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle'],
|
||||
borderWidth: ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
||||
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
||||
marginHorizontal: ['marginRight', 'marginLeft'],
|
||||
marginVertical: ['marginTop', 'marginBottom'],
|
||||
overflow: ['overflowX', 'overflowY'],
|
||||
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
||||
paddingHorizontal: ['paddingRight', 'paddingLeft'],
|
||||
paddingVertical: ['paddingTop', 'paddingBottom'],
|
||||
textDecorationLine: ['textDecoration'],
|
||||
writingDirection: ['direction']
|
||||
};
|
||||
|
||||
const colorProps = {
|
||||
backgroundColor: true,
|
||||
borderColor: true,
|
||||
borderTopColor: true,
|
||||
borderRightColor: true,
|
||||
borderBottomColor: true,
|
||||
borderLeftColor: true,
|
||||
color: true
|
||||
};
|
||||
|
||||
const alphaSortProps = propsArray =>
|
||||
propsArray.sort((a, b) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
/**
|
||||
* Shadow
|
||||
*/
|
||||
|
||||
// TODO: add inset and spread support
|
||||
const resolveShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.shadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
|
||||
const color = processColor(style.shadowColor, style.shadowOpacity);
|
||||
|
||||
if (color) {
|
||||
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
|
||||
} else if (style.boxShadow) {
|
||||
resolvedStyle.boxShadow = style.boxShadow;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text Shadow
|
||||
*/
|
||||
|
||||
const resolveTextShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.textShadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
|
||||
const color = processColor(style.textShadowColor);
|
||||
|
||||
if (color) {
|
||||
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform
|
||||
*/
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
const mapTransform = transform => {
|
||||
const type = Object.keys(transform)[0];
|
||||
const value = normalizeValue(type, transform[type]);
|
||||
return `${type}(${value})`;
|
||||
};
|
||||
|
||||
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
|
||||
const convertTransformMatrix = transformMatrix => {
|
||||
const matrix = transformMatrix.join(',');
|
||||
return `matrix3d(${matrix})`;
|
||||
};
|
||||
|
||||
const resolveTransform = (resolvedStyle, style) => {
|
||||
let transform = style.transform;
|
||||
if (Array.isArray(style.transform)) {
|
||||
transform = style.transform.map(mapTransform).join(' ');
|
||||
} else if (style.transformMatrix) {
|
||||
transform = convertTransformMatrix(style.transformMatrix);
|
||||
}
|
||||
resolvedStyle.transform = transform;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reducer
|
||||
*/
|
||||
|
||||
const createReducer = (style, styleProps) => {
|
||||
let hasResolvedShadow = false;
|
||||
let hasResolvedTextShadow = false;
|
||||
|
||||
return (resolvedStyle, prop) => {
|
||||
const value = normalizeValue(prop, style[prop]);
|
||||
if (value == null) {
|
||||
return resolvedStyle;
|
||||
}
|
||||
|
||||
switch (prop) {
|
||||
case 'display': {
|
||||
resolvedStyle.display = value;
|
||||
// default of 'flexShrink:0' has lowest precedence
|
||||
if (style.display === 'flex' && style.flex == null && style.flexShrink == null) {
|
||||
resolvedStyle.flexShrink = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// ignore React Native styles
|
||||
case 'aspectRatio':
|
||||
case 'elevation':
|
||||
case 'overlayColor':
|
||||
case 'resizeMode':
|
||||
case 'tintColor': {
|
||||
break;
|
||||
}
|
||||
case 'flex': {
|
||||
resolvedStyle.flexGrow = value;
|
||||
resolvedStyle.flexShrink = 1;
|
||||
resolvedStyle.flexBasis = 'auto';
|
||||
break;
|
||||
}
|
||||
case 'shadowColor':
|
||||
case 'shadowOffset':
|
||||
case 'shadowOpacity':
|
||||
case 'shadowRadius': {
|
||||
if (!hasResolvedShadow) {
|
||||
resolveShadow(resolvedStyle, style);
|
||||
}
|
||||
hasResolvedShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'textAlignVertical': {
|
||||
resolvedStyle.verticalAlign = value === 'center' ? 'middle' : value;
|
||||
break;
|
||||
}
|
||||
case 'textShadowColor':
|
||||
case 'textShadowOffset':
|
||||
case 'textShadowRadius': {
|
||||
if (!hasResolvedTextShadow) {
|
||||
resolveTextShadow(resolvedStyle, style);
|
||||
}
|
||||
hasResolvedTextShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'transform':
|
||||
case 'transformMatrix': {
|
||||
resolveTransform(resolvedStyle, style);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// normalize color values
|
||||
let finalValue = value;
|
||||
if (colorProps[prop]) {
|
||||
finalValue = processColor(value);
|
||||
}
|
||||
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// the value of any longform property in the original styles takes
|
||||
// precedence over the shortform's value
|
||||
if (styleProps.indexOf(longForm) === -1) {
|
||||
resolvedStyle[longForm] = finalValue;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolvedStyle[prop] = finalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedStyle;
|
||||
};
|
||||
};
|
||||
|
||||
const createReactDOMStyle = style => {
|
||||
if (!style) {
|
||||
return emptyObject;
|
||||
}
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
const reducer = createReducer(style, styleProps);
|
||||
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
|
||||
return resolvedStyle;
|
||||
};
|
||||
|
||||
module.exports = createReactDOMStyle;
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* The browser implements the CSS cascade, where the order of properties is a
|
||||
* factor in determining which styles to paint. React Native is different in
|
||||
* giving precedence to the more specific styles. For example, the value of
|
||||
* `paddingTop` takes precedence over that of `padding`.
|
||||
*
|
||||
* This module creates mutally exclusive style declarations by expanding all of
|
||||
* React Native's supported shortform properties (e.g. `padding`) to their
|
||||
* longfrom equivalents.
|
||||
*/
|
||||
|
||||
import normalizeValue from './normalizeValue';
|
||||
import processColor from '../../modules/processColor';
|
||||
import resolveBoxShadow from './resolveBoxShadow';
|
||||
import resolveTextShadow from './resolveTextShadow';
|
||||
import resolveTransform from './resolveTransform';
|
||||
|
||||
const emptyObject = {};
|
||||
const styleShortFormProperties = {
|
||||
borderColor: ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
||||
borderRadius: [
|
||||
'borderTopLeftRadius',
|
||||
'borderTopRightRadius',
|
||||
'borderBottomRightRadius',
|
||||
'borderBottomLeftRadius'
|
||||
],
|
||||
borderStyle: ['borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle'],
|
||||
borderWidth: ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
||||
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
||||
marginHorizontal: ['marginRight', 'marginLeft'],
|
||||
marginVertical: ['marginTop', 'marginBottom'],
|
||||
overflow: ['overflowX', 'overflowY'],
|
||||
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
||||
paddingHorizontal: ['paddingRight', 'paddingLeft'],
|
||||
paddingVertical: ['paddingTop', 'paddingBottom'],
|
||||
textDecorationLine: ['textDecoration'],
|
||||
writingDirection: ['direction']
|
||||
};
|
||||
|
||||
const colorProps = {
|
||||
backgroundColor: true,
|
||||
borderColor: true,
|
||||
borderTopColor: true,
|
||||
borderRightColor: true,
|
||||
borderBottomColor: true,
|
||||
borderLeftColor: true,
|
||||
color: true
|
||||
};
|
||||
|
||||
const alphaSortProps = propsArray =>
|
||||
propsArray.sort((a, b) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const createReducer = (style, styleProps) => {
|
||||
let hasResolvedBoxShadow = false;
|
||||
let hasResolvedTextShadow = false;
|
||||
|
||||
return (resolvedStyle, prop) => {
|
||||
const value = normalizeValue(prop, style[prop]);
|
||||
if (value == null) {
|
||||
return resolvedStyle;
|
||||
}
|
||||
|
||||
switch (prop) {
|
||||
case 'display': {
|
||||
resolvedStyle.display = value;
|
||||
// default of 'flexShrink:0' has lowest precedence
|
||||
if (style.display === 'flex' && style.flex == null && style.flexShrink == null) {
|
||||
resolvedStyle.flexShrink = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// ignore React Native styles
|
||||
case 'aspectRatio':
|
||||
case 'elevation':
|
||||
case 'overlayColor':
|
||||
case 'resizeMode':
|
||||
case 'tintColor': {
|
||||
break;
|
||||
}
|
||||
case 'flex': {
|
||||
resolvedStyle.flexGrow = value;
|
||||
resolvedStyle.flexShrink = 1;
|
||||
resolvedStyle.flexBasis = 'auto';
|
||||
break;
|
||||
}
|
||||
case 'shadowColor':
|
||||
case 'shadowOffset':
|
||||
case 'shadowOpacity':
|
||||
case 'shadowRadius': {
|
||||
if (!hasResolvedBoxShadow) {
|
||||
resolveBoxShadow(resolvedStyle, style);
|
||||
}
|
||||
hasResolvedBoxShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'textAlignVertical': {
|
||||
resolvedStyle.verticalAlign = value === 'center' ? 'middle' : value;
|
||||
break;
|
||||
}
|
||||
case 'textShadowColor':
|
||||
case 'textShadowOffset':
|
||||
case 'textShadowRadius': {
|
||||
if (!hasResolvedTextShadow) {
|
||||
resolveTextShadow(resolvedStyle, style);
|
||||
}
|
||||
hasResolvedTextShadow = true;
|
||||
break;
|
||||
}
|
||||
case 'transform': {
|
||||
resolveTransform(resolvedStyle, style);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// normalize color values
|
||||
let finalValue = value;
|
||||
if (colorProps[prop]) {
|
||||
finalValue = processColor(value);
|
||||
}
|
||||
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// the value of any longform property in the original styles takes
|
||||
// precedence over the shortform's value
|
||||
if (styleProps.indexOf(longForm) === -1) {
|
||||
resolvedStyle[longForm] = finalValue;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolvedStyle[prop] = finalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedStyle;
|
||||
};
|
||||
};
|
||||
|
||||
const expandStyle = style => {
|
||||
if (!style) {
|
||||
return emptyObject;
|
||||
}
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
const reducer = createReducer(style, styleProps);
|
||||
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
|
||||
return resolvedStyle;
|
||||
};
|
||||
|
||||
module.exports = expandStyle;
|
||||
@@ -1,22 +0,0 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
import processColor from '../../modules/processColor';
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
// TODO: add inset and spread support
|
||||
const resolveBoxShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.shadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
|
||||
const color = processColor(style.shadowColor, style.shadowOpacity);
|
||||
|
||||
if (color) {
|
||||
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
|
||||
} else if (style.boxShadow) {
|
||||
resolvedStyle.boxShadow = style.boxShadow;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = resolveBoxShadow;
|
||||
@@ -1,18 +0,0 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
import processColor from '../../modules/processColor';
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
const resolveTextShadow = (resolvedStyle, style) => {
|
||||
const { height, width } = style.textShadowOffset || defaultOffset;
|
||||
const offsetX = normalizeValue(null, width);
|
||||
const offsetY = normalizeValue(null, height);
|
||||
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
|
||||
const color = processColor(style.textShadowColor);
|
||||
|
||||
if (color) {
|
||||
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = resolveTextShadow;
|
||||
@@ -1,27 +0,0 @@
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
const mapTransform = transform => {
|
||||
const type = Object.keys(transform)[0];
|
||||
const value = normalizeValue(type, transform[type]);
|
||||
return `${type}(${value})`;
|
||||
};
|
||||
|
||||
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
|
||||
const convertTransformMatrix = transformMatrix => {
|
||||
const matrix = transformMatrix.join(',');
|
||||
return `matrix3d(${matrix})`;
|
||||
};
|
||||
|
||||
const resolveTransform = (resolvedStyle, style) => {
|
||||
let transform = style.transform;
|
||||
if (Array.isArray(style.transform)) {
|
||||
transform = style.transform.map(mapTransform).join(' ');
|
||||
} else if (style.transformMatrix) {
|
||||
transform = convertTransformMatrix(style.transformMatrix);
|
||||
}
|
||||
resolvedStyle.transform = transform;
|
||||
};
|
||||
|
||||
module.exports = resolveTransform;
|
||||
@@ -87,6 +87,7 @@ export default class ScrollViewBase extends Component {
|
||||
|
||||
_handleScroll = e => {
|
||||
e.persist();
|
||||
e.stopPropagation();
|
||||
const { scrollEventThrottle } = this.props;
|
||||
// A scroll happened, so the scroll bumps the debounce.
|
||||
this._debouncedOnScrollEnd(e);
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
|
||||
import createDOMProps from '../createDOMProps';
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import i18nStyle from '../../apis/StyleSheet/i18nStyle';
|
||||
import StyleRegistry from '../../apis/StyleSheet/registry';
|
||||
import UIManager from '../../apis/UIManager';
|
||||
|
||||
const hyphenPattern = /-([a-z])/g;
|
||||
const toCamelCase = str => str.replace(hyphenPattern, m => m[1].toUpperCase());
|
||||
|
||||
const NativeMethodsMixin = {
|
||||
/**
|
||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||
@@ -69,16 +73,24 @@ const NativeMethodsMixin = {
|
||||
setNativeProps(nativeProps: Object) {
|
||||
// Copy of existing DOM state
|
||||
const node = findNodeHandle(this);
|
||||
const nodeStyle = node.style;
|
||||
const classList = Array.prototype.slice.call(node.classList);
|
||||
const style = { ...node.style };
|
||||
// See #454
|
||||
delete style.length;
|
||||
|
||||
const style = {};
|
||||
// DOM style is a CSSStyleDeclaration
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
|
||||
for (let i = 0; i < node.style.length; i += 1) {
|
||||
const property = nodeStyle.item(i);
|
||||
if (property) {
|
||||
// DOM style uses hyphenated prop names and may include vendor prefixes
|
||||
// Transform back into React DOM style.
|
||||
style[toCamelCase(property)] = nodeStyle.getPropertyValue(property);
|
||||
}
|
||||
}
|
||||
const domStyleProps = { classList, style };
|
||||
|
||||
// Next DOM state
|
||||
const domProps = createDOMProps(nativeProps, style =>
|
||||
StyleRegistry.resolveStateful(style, domStyleProps)
|
||||
const domProps = createDOMProps(i18nStyle(nativeProps), style =>
|
||||
StyleRegistry.resolveStateful(style, domStyleProps, { i18n: false })
|
||||
);
|
||||
UIManager.updateView(node, domProps, this);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user