Compare commits

...

13 Commits

Author SHA1 Message Date
Nicolas Gallagher
d994a25017 0.0.90 2017-04-28 15:17:44 -07:00
Nicolas Gallagher
756df70154 [fix] check 'transform' style is array before mapping 2017-04-28 15:15:57 -07:00
Nicolas Gallagher
f0b06419f9 Move prefixStyles module 2017-04-27 16:27:45 -07:00
Nicolas Gallagher
60ff75705e [fix] remove stray 'length' property from style object
Fix #454
2017-04-27 15:10:03 -07:00
Nicolas Gallagher
5e8ad67296 0.0.89 2017-04-26 15:12:19 -07:00
Nathan Broadbent
ba24a882be Link to another starter kit example 2017-04-26 15:11:16 -07:00
Nicolas Gallagher
c7686209cd Update prettier 2017-04-26 15:07:44 -07:00
Nicolas Gallagher
f1b281ae32 Update debounce dependency 2017-04-26 15:06:41 -07:00
Nicolas Gallagher
b676fbd5e0 [fix] propTypes removal in production builds
Updates the relevant babel plugin, which now replaces component
propTypes with an empty object, avoiding the majority of potential
runtime errors related to this transform.

Fix #423
2017-04-26 11:05:33 -07:00
Nicolas Gallagher
51aef6c791 0.0.88 2017-04-24 13:28:38 -07:00
Nathan Leung
ae9a9cde5f Fix example webpack config in documentation 2017-04-24 13:21:46 -07:00
Nicolas Gallagher
beb907b180 Rename some variables in StyleRegistry 2017-04-24 13:21:27 -07:00
Nicolas Gallagher
a3362e1f38 [fix] setNativeProps inline styles
Inline styles are preserved when using 'setNativeProps'. Adds unit tests
for the resolution logic required by 'setNativeProps'/'resolveStateful'
in a DOM context.

Fix #439
2017-04-23 21:24:27 -07:00
19 changed files with 185 additions and 114 deletions

View File

@@ -144,6 +144,7 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
* [reactxp](https://github.com/microsoft/reactxp)
* [react-web](https://github.com/taobaofed/react-web)
* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)
## License

View File

@@ -61,7 +61,7 @@ module.exports = {
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
},
],
resolve: {
// Maps the 'react-native' import to 'react-native-web'.
@@ -76,6 +76,10 @@ module.exports = {
}
```
A more complex example setup for web apps can be found in various starter kits
(e.g., create-react-app and
[react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack))
Please refer to the Webpack documentation for more information.
## Jest

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.87",
"version": "0.0.90",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -27,7 +27,7 @@
"array-find-index": "^1.0.2",
"babel-runtime": "^6.23.0",
"create-react-class": "^15.5.2",
"debounce": "^1.0.0",
"debounce": "1.0.2",
"deep-assign": "^2.0.0",
"fbjs": "^0.8.8",
"hyphenate-style-name": "^1.0.2",
@@ -42,19 +42,19 @@
"babel-core": "^6.24.1",
"babel-eslint": "^7.2.2",
"babel-loader": "^6.4.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.2",
"babel-preset-react-native": "^1.9.1",
"del-cli": "^0.2.1",
"enzyme": "^2.8.2",
"enzyme-to-json": "^1.5.1",
"eslint": "^3.19.0",
"eslint-config-prettier": "^1.6.0",
"eslint-config-prettier": "^1.7.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-react": "^6.10.3",
"file-loader": "^0.11.1",
"jest": "^19.0.2",
"node-libs-browser": "^0.5.3",
"prettier": "^1.1.0",
"prettier": "^1.2.2",
"react": "~15.4.1",
"react-addons-test-utils": "~15.4.1",
"react-dom": "~15.4.1",

View File

@@ -49,7 +49,7 @@ module.exports = {
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../src/module')
'react-native': path.join(__dirname, '../src')
}
}
};

View File

@@ -299,7 +299,8 @@ var PanResponder = {
PanResponder._initializeGestureState(gestureState);
}
} else if (
e.nativeEvent.originalEvent && e.nativeEvent.originalEvent.type === 'mousedown'
e.nativeEvent.originalEvent &&
e.nativeEvent.originalEvent.type === 'mousedown'
) {
PanResponder._initializeGestureState(gestureState);
}

View File

@@ -7,10 +7,12 @@ import flattenArray from '../../modules/flattenArray';
import flattenStyle from './flattenStyle';
import I18nManager from '../I18nManager';
import mapKeyValue from '../../modules/mapKeyValue';
import prefixInlineStyles from './prefixInlineStyles';
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}`;
@@ -81,36 +83,58 @@ class StyleRegistry {
/**
* Resolves a React Native style object to DOM attributes, accounting for
* the existing styles applied to the DOM node
* the existing styles applied to the DOM node.
*
* To determine the next style, some of the existing DOM state must be
* converted back into React Native styles.
*/
resolveStateful(reactNativeStyle, domClassList) {
const previousReactNativeStyle = {};
const preservedClassNames = [];
resolveStateful(rnStyleNext, { classList: domClassList, style: domStyle }) {
// Convert the DOM classList back into a React Native form
// Preserves unrecognized class names.
const { classList: rnClassList, style: rnStyle } = domClassList.reduce(
(styleProps, className) => {
const { prop, value } = this.styleManager.getDeclaration(className);
if (prop) {
styleProps.style[prop] = value;
} else {
styleProps.classList.push(className);
}
return styleProps;
},
{ classList: [], style: {} }
);
// Convert the existing classList to a React Native style and preserve any
// unrecognized classNames.
domClassList.forEach(className => {
const { prop, value } = this.styleManager.getDeclaration(className);
if (prop) {
previousReactNativeStyle[prop] = value;
} else {
preservedClassNames.push(className);
// 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
]);
// Next class names take priority over current inline styles
const style = { ...rdomStyle };
rdomClassListNext.forEach(className => {
const { prop } = this.styleManager.getDeclaration(className);
if (style[prop]) {
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));
// Resolve the two React Native styles.
const { classList, style = {} } = this.resolve([previousReactNativeStyle, reactNativeStyle]);
// Because this is used in stateful operations we need to remove any
// existing inline styles that would override the classNames.
classList.forEach(className => {
const { prop } = this.styleManager.getDeclaration(className);
style[prop] = null;
});
classList.push(preservedClassNames);
const className = classListToString(classList);
return { className, style };
}

View File

@@ -58,19 +58,37 @@ describe('apis/StyleSheet/StyleRegistry', () => {
});
});
test('resolveStateful', () => {
// generate a classList to act as pre-existing DOM state
const mockStyle = styleRegistry.register({
borderWidth: 0,
borderColor: 'red',
width: 100
describe('resolveStateful', () => {
test('preserves unrelated class names', () => {
const domStyleProps = { classList: ['unknown-class-1', 'unknown-class-2'], style: {} };
const domStyleNextProps = styleRegistry.resolveStateful({}, domStyleProps);
expect(domStyleNextProps).toMatchSnapshot();
});
const { classList: domClassList } = styleRegistry.resolve(mockStyle);
domClassList.unshift('external-className');
expect(domClassList).toMatchSnapshot();
// test result
const result = styleRegistry.resolveStateful({ borderWidth: 1, opacity: 1 }, domClassList);
expect(result).toMatchSnapshot();
test('preserves unrelated inline styles', () => {
const domStyleProps = { classList: [], style: { fontSize: '20px' } };
const domStyleNextProps = styleRegistry.resolveStateful({ opacity: 1 }, domStyleProps);
expect(domStyleNextProps).toMatchSnapshot();
});
test('next class names have priority over current inline styles', () => {
const domStyleProps = { classList: [], style: { opacity: 0.5 } };
const nextStyle = styleRegistry.register({ opacity: 1 });
const domStyleNextProps = styleRegistry.resolveStateful(nextStyle, domStyleProps);
expect(domStyleNextProps).toMatchSnapshot();
});
test('next inline styles have priority over current inline styles', () => {
// 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)' }
};
const domStyleNextProps = styleRegistry.resolveStateful(
{ opacity: 1, transform: [{ scale: 2 }] },
domStyleProps
);
expect(domStyleNextProps).toMatchSnapshot();
});
});
});

View File

@@ -192,35 +192,39 @@ Object {
}
`;
exports[`apis/StyleSheet/StyleRegistry resolveStateful 1`] = `
Array [
"external-className",
"rn-borderTopColor-1gxhl28",
"rn-borderRightColor-knoah9",
"rn-borderBottomColor-1ani3fp",
"rn-borderLeftColor-ribj9x",
"rn-borderTopWidth-13yce4e",
"rn-borderRightWidth-fnigne",
"rn-borderBottomWidth-ndvcnb",
"rn-borderLeftWidth-gxnn5r",
"rn-width-b8lwoo",
]
`;
exports[`apis/StyleSheet/StyleRegistry resolveStateful 2`] = `
exports[`apis/StyleSheet/StyleRegistry resolveStateful next class names have priority over current inline styles 1`] = `
Object {
"className": "rn-borderBottomColor-1ani3fp rn-borderBottomWidth-ndvcnb rn-borderLeftColor-ribj9x rn-borderLeftWidth-gxnn5r rn-borderRightColor-knoah9 rn-borderRightWidth-fnigne rn-borderTopColor-1gxhl28 rn-borderTopWidth-13yce4e rn-width-b8lwoo external-className",
"className": "rn-opacity-6dt33c",
"style": Object {
"borderBottomColor": null,
"borderBottomWidth": null,
"borderLeftColor": null,
"borderLeftWidth": null,
"borderRightColor": null,
"borderRightWidth": null,
"borderTopColor": null,
"borderTopWidth": null,
"opacity": 1,
"width": null,
"opacity": "",
},
}
`;
exports[`apis/StyleSheet/StyleRegistry resolveStateful next inline styles have priority over current inline styles 1`] = `
Object {
"className": "",
"style": Object {
"WebkitTransform": "scale(2)",
"opacity": 1,
"transform": "scale(2)",
},
}
`;
exports[`apis/StyleSheet/StyleRegistry resolveStateful preserves unrelated class names 1`] = `
Object {
"className": "unknown-class-1 unknown-class-2",
"style": Object {},
}
`;
exports[`apis/StyleSheet/StyleRegistry resolveStateful preserves unrelated inline styles 1`] = `
Object {
"className": "",
"style": Object {
"fontSize": "20px",
"opacity": 1,
},
}
`;

View File

@@ -3,7 +3,18 @@
import resolveTransform from '../resolveTransform';
describe('apis/StyleSheet/resolveTransform', () => {
test('transform', () => {
// 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' }]

View File

@@ -1,7 +1,7 @@
import hyphenateStyleName from 'hyphenate-style-name';
import mapKeyValue from '../../modules/mapKeyValue';
import normalizeValue from './normalizeValue';
import prefixAll from 'inline-style-prefixer/static';
import prefixStyles from '../../modules/prefixStyles';
const createDeclarationString = (prop, val) => {
const name = hyphenateStyleName(prop);
@@ -19,6 +19,6 @@ const createDeclarationString = (prop, val) => {
* // => 'color:blue;width:20px'
*/
const generateCss = style =>
mapKeyValue(prefixAll(style), createDeclarationString).sort().join(';');
mapKeyValue(prefixStyles(style), createDeclarationString).sort().join(';');
module.exports = generateCss;

View File

@@ -71,15 +71,17 @@ const i18nStyle = originalStyle => {
continue;
}
const value = style[prop];
if (PROPERTIES_TO_SWAP[prop]) {
const newProp = flipProperty(prop);
nextStyle[newProp] = style[prop];
nextStyle[newProp] = value;
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
nextStyle[prop] = swapLeftRight(style[prop]);
nextStyle[prop] = swapLeftRight(value);
} else if (prop === 'textShadowOffset') {
nextStyle[prop] = style[prop];
nextStyle[prop].width = additiveInverse(style[prop].width);
} else if (prop === 'transform') {
nextStyle[prop] = value;
nextStyle[prop].width = additiveInverse(value.width);
} else if (prop === 'transform' && Array.isArray(value)) {
nextStyle[prop] = style[prop].map(flipTransform);
} else {
nextStyle[prop] = style[prop];

View File

@@ -15,13 +15,13 @@ const convertTransformMatrix = transformMatrix => {
};
const resolveTransform = (resolvedStyle, style) => {
let transform = style.transform;
if (Array.isArray(style.transform)) {
const transform = style.transform.map(mapTransform).join(' ');
resolvedStyle.transform = transform;
transform = style.transform.map(mapTransform).join(' ');
} else if (style.transformMatrix) {
const transform = convertTransformMatrix(style.transformMatrix);
resolvedStyle.transform = transform;
transform = convertTransformMatrix(style.transformMatrix);
}
resolvedStyle.transform = transform;
};
module.exports = resolveTransform;

View File

@@ -206,7 +206,8 @@ class ListView extends Component {
totalIndex++;
if (
renderSeparator && (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)
renderSeparator &&
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)
) {
const adjacentRowHighlighted =
this.state.highlightedRow.sectionID === sectionID &&

View File

@@ -67,12 +67,18 @@ const NativeMethodsMixin = {
* the initial styles from the DOM node and merge them with incoming props.
*/
setNativeProps(nativeProps: Object) {
// DOM state
// Copy of existing DOM state
const node = findNodeHandle(this);
const classList = Array.prototype.slice.call(node.classList);
const style = { ...node.style };
// See #454
delete style.length;
const domStyleProps = { classList, style };
// Next DOM state
const domProps = createDOMProps(nativeProps, style =>
StyleRegistry.resolveStateful(style, classList)
StyleRegistry.resolveStateful(style, domStyleProps)
);
UIManager.updateView(node, domProps, this);
}

View File

@@ -75,7 +75,10 @@ const applyLayout = Component => {
if (!this._isMounted) return;
if (
layout.x !== x || layout.y !== y || layout.width !== width || layout.height !== height
layout.x !== x ||
layout.y !== y ||
layout.width !== width ||
layout.height !== height
) {
this._layoutState = { x, y, width, height };
const nativeEvent = { layout: this._layoutState };

View File

@@ -1,9 +1,9 @@
/* eslint-env jasmine, jest */
import prefixInlineStyles from '../prefixInlineStyles';
import { prefixInlineStyles } from '..';
describe('apis/StyleSheet/prefixInlineStyles', () => {
test('handles array values', () => {
describe('modules/prefixStyles', () => {
test('handles array values for inline styles', () => {
const style = {
display: ['-webkit-flex', 'flex']
};

View File

@@ -1,6 +1,8 @@
import prefixAll from 'inline-style-prefixer/static';
const prefixInlineStyles = style => {
export default prefixAll;
export const prefixInlineStyles = style => {
const prefixedStyles = prefixAll(style);
// React@15 removed undocumented support for fallback values in
@@ -14,5 +16,3 @@ const prefixInlineStyles = style => {
return prefixedStyles;
};
module.exports = prefixInlineStyles;

View File

@@ -158,7 +158,9 @@ function dangerousStyleValue(name, value, component) {
var isNonNumeric = isNaN(value);
if (
isNonNumeric || value === 0 || (unitlessNumbers.hasOwnProperty(name) && unitlessNumbers[name])
isNonNumeric ||
value === 0 ||
(unitlessNumbers.hasOwnProperty(name) && unitlessNumbers[name])
) {
return '' + value; // cast to string
}

View File

@@ -931,9 +931,9 @@ babel-plugin-transform-react-jsx@^6.3.13, babel-plugin-transform-react-jsx@^6.5.
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.0.0"
babel-plugin-transform-react-remove-prop-types@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.0.tgz#f63840e7953563d661be8c647b094d74d7363f17"
babel-plugin-transform-react-remove-prop-types@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.2.tgz#97e3cab4d05938df91e16c0473737ef6c575a2e6"
babel-plugin-transform-regenerator@6.16.1:
version "6.16.1"
@@ -1917,19 +1917,13 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
date-now@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-1.0.1.tgz#bb7d086438debe4182a485fb3df3fbfb99d6153c"
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
debounce:
version "1.0.0"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.0.0.tgz#0948af513d2e4ce407916f8506a423d3f9cf72d8"
dependencies:
date-now "1.0.1"
debounce@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.0.2.tgz#503cc674d8d7f737099664fb75ddbd36b9626dc6"
debug@2.6.1:
version "2.6.1"
@@ -2330,9 +2324,9 @@ escope@^3.6.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-config-prettier@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-1.6.0.tgz#56e53a8eb461c06eced20cec40d765c185100fd5"
eslint-config-prettier@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-1.7.0.tgz#cda3ce22df1e852daa9370f1f3446e8b8a02ce44"
dependencies:
get-stdin "^5.0.1"
@@ -5012,9 +5006,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.1.0.tgz#9d6ad005703efefa66b6999b8916bfc6afeaf9f8"
prettier@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.2.2.tgz#22d17c1132faaaea1f1d4faea31f19f7a1959f3e"
dependencies:
ast-types "0.9.8"
babel-code-frame "6.22.0"