mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 23:23:35 +08:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c37a42566 | ||
|
|
c38369ac0f | ||
|
|
03769f7d45 | ||
|
|
eb43a8f3e7 | ||
|
|
cdf13b880d | ||
|
|
47fad1ef58 | ||
|
|
5f69c8e8b8 | ||
|
|
21550db5f2 | ||
|
|
1cae5d55a1 | ||
|
|
11d23f850a | ||
|
|
d994a25017 | ||
|
|
756df70154 | ||
|
|
f0b06419f9 | ||
|
|
60ff75705e | ||
|
|
5e8ad67296 | ||
|
|
ba24a882be | ||
|
|
c7686209cd | ||
|
|
f1b281ae32 | ||
|
|
b676fbd5e0 | ||
|
|
51aef6c791 | ||
|
|
ae9a9cde5f | ||
|
|
beb907b180 | ||
|
|
a3362e1f38 | ||
|
|
64d2d34367 | ||
|
|
d83cd45b6f | ||
|
|
227971d22c | ||
|
|
4822cf4620 | ||
|
|
91e4528eac | ||
|
|
1ee64d8285 | ||
|
|
66a4c13bf3 | ||
|
|
9012e98ba7 | ||
|
|
046e01dfa9 | ||
|
|
6e71e1e058 | ||
|
|
d5a9f3e779 | ||
|
|
f16f5f21ce | ||
|
|
0bb7e67e63 | ||
|
|
c6b54930b6 | ||
|
|
599f1fcaf5 | ||
|
|
3f7a4e455f | ||
|
|
1f3e9cc6ee | ||
|
|
17ed63129f | ||
|
|
769334d04e |
@@ -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
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ When `false`, the text is not selectable.
|
||||
+ `textAlign`
|
||||
+ `textAlignVertical`
|
||||
+ `textDecorationLine`
|
||||
+ `textIndent` ‡
|
||||
+ `textOverflow` ‡
|
||||
+ `textRendering` ‡
|
||||
+ `textShadowColor`
|
||||
|
||||
@@ -186,16 +186,30 @@ Controls whether the View can be the target of touch events. The enhanced
|
||||
+ `borderRightWidth`
|
||||
+ `borderTopWidth`
|
||||
+ `bottom`
|
||||
+ `boxShadow`
|
||||
+ `boxShadow` ‡
|
||||
+ `boxSizing`
|
||||
+ `clip` ‡
|
||||
+ `cursor` ‡
|
||||
+ `display`
|
||||
+ `filter` ‡
|
||||
+ `flex` (number)
|
||||
+ `flexBasis`
|
||||
+ `flexDirection`
|
||||
+ `flexGrow`
|
||||
+ `flexShrink`
|
||||
+ `flexWrap`
|
||||
+ `gridAutoColumns` ‡
|
||||
+ `gridAutoFlow` ‡
|
||||
+ `gridAutoRows` ‡
|
||||
+ `gridColumnEnd` ‡
|
||||
+ `gridColumnGap` ‡
|
||||
+ `gridColumnStart` ‡
|
||||
+ `gridRowEnd` ‡
|
||||
+ `gridRowGap` ‡
|
||||
+ `gridRowStart` ‡
|
||||
+ `gridTemplateColumns` ‡
|
||||
+ `gridTemplateRows` ‡
|
||||
+ `gridTemplateAreas` ‡
|
||||
+ `height`
|
||||
+ `justifyContent`
|
||||
+ `left`
|
||||
@@ -213,6 +227,7 @@ Controls whether the View can be the target of touch events. The enhanced
|
||||
+ `opacity`
|
||||
+ `order`
|
||||
+ `outline` ‡
|
||||
+ `outlineColor` ‡
|
||||
+ `overflow`
|
||||
+ `overflowX` ‡
|
||||
+ `overflowY` ‡
|
||||
@@ -227,6 +242,10 @@ Controls whether the View can be the target of touch events. The enhanced
|
||||
+ `perspectiveOrigin` ‡
|
||||
+ `position`
|
||||
+ `right`
|
||||
+ `shadowColor`
|
||||
+ `shadowOffset`
|
||||
+ `shadowOpacity`
|
||||
+ `shadowRadius`
|
||||
+ `top`
|
||||
+ `transform`
|
||||
+ `transformOrigin` ‡
|
||||
|
||||
@@ -39,7 +39,10 @@ using `aria-label`.
|
||||
|
||||
In some cases, we also want to alert the end user of the type of selected
|
||||
component (i.e., that it is a “button”). To provide more context to screen
|
||||
readers, you should specify the `accessibilityRole` property.
|
||||
readers, you should specify the `accessibilityRole` property. (Note that React
|
||||
Native for Web provides a compatibility mapping of equivalent
|
||||
`accessibilityTraits` and `accessibilityComponentType` values to
|
||||
`accessibilityRole`).
|
||||
|
||||
The `accessibilityRole` prop is used to infer an [analogous HTML
|
||||
element][html-aria-url] to use in addition to the resulting ARIA `role`, where
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Getting started
|
||||
|
||||
It is recommended that your application provide a `Promise` and `Array.from`
|
||||
polyfill.
|
||||
|
||||
## Webpack and Babel
|
||||
|
||||
[Webpack](webpack.js.org) is a popular build tool for web apps. Below is an
|
||||
@@ -58,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'.
|
||||
@@ -73,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
|
||||
|
||||
@@ -27,7 +27,7 @@ module.exports = {
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': path.join(__dirname, '../../src')
|
||||
'react-native': path.join(__dirname, '../../src/module')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ var styles = StyleSheet.create({
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
container: {
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.84",
|
||||
"version": "0.0.94",
|
||||
"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",
|
||||
|
||||
@@ -22,18 +22,19 @@ Typical render timings*: mean ± two standard deviations
|
||||
|
||||
| Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
|
||||
| :--- | ---: | ---: | ---: |
|
||||
| `css-modules` | `87.68` `±13.29` | `171.96` `±14.91` | |
|
||||
| `react-native-web/stylesheet@0.0.81` | `90.59` `±12.03` | `190.37` `±19.65` | |
|
||||
| `react-native-web@0.0.81` | `105.20` `±17.86` | `226.54` `±29.50` | `12.12` `±6.94ms` |
|
||||
| `css-modules` | `87.67` `±15.22` | `170.85` `±16.87` | |
|
||||
| `react-native-web/stylesheet@0.0.84` | `90.02` `±13.16` | `186.66` `±19.23` | |
|
||||
| `react-native-web@0.0.84` | `102.72` `±19.26` | `222.35` `±18.95` | `12.81` `±5.45ms` |
|
||||
|
||||
Other libraries
|
||||
|
||||
| Implementation | Deep tree (ms) | Wide tree (ms) |
|
||||
| :--- | ---: | ---: |
|
||||
| `aphrodite@1.2.0` | `101.25` `±18.78` | `224.59` `±22.28` |
|
||||
| `glamor@3.0.0-1` | `143.39` `±23.05` | `275.21` `±21.10` |
|
||||
| `react-jss@5.4.1` | `142.27` `±16.62` | `318.62` `±26.19` |
|
||||
| `reactxp@0.34.3` | `221.36` `±23.35` | `472.61` `±40.86` |
|
||||
| `styled-components@2.0.0-7` | `301.92` `±39.43` | `647.80` `±102.1` |
|
||||
| `styletron@2.5.1` | `88.48` `±12.00` | `171.89` `±13.28` |
|
||||
| `aphrodite@1.2.0` | `101.32` `±20.33` | `220.33` `±31.41` |
|
||||
| `glamor@3.0.0-1` | `129.00` `±14.92` | `264.57` `±28.54` |
|
||||
| `react-jss@5.4.1` | `137.33` `±21.55` | `314.91` `±29.03` |
|
||||
| `reactxp@0.34.3` | `223.82` `±32.77` | `461.56` `±34.43` |
|
||||
| `styled-components@2.0.0-11` | `277.53` `±28.83` | `627.91` `±43.13` |
|
||||
|
||||
*MacBook Pro (13-inch, Early 2011); 2.7 GHz Intel Core i7; 16 GB 1600 MHz DDR3. Google Chrome 56.
|
||||
|
||||
@@ -6,7 +6,9 @@ import React from 'react';
|
||||
|
||||
export const styletron = new Styletron();
|
||||
|
||||
const View = ({ style, ...other }) => <div {...other} className={classnames(viewStyle, ...style)} />;
|
||||
const View = ({ style, ...other }) => (
|
||||
<div {...other} className={classnames(viewStyle, ...style)} />
|
||||
);
|
||||
|
||||
const viewStyle = injectStylePrefixed(styletron, {
|
||||
alignItems: 'stretch',
|
||||
|
||||
@@ -13,10 +13,10 @@ const AppStates = {
|
||||
const listeners = [];
|
||||
|
||||
class AppState {
|
||||
static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState;
|
||||
static isAvailable = ExecutionEnvironment.canUseDOM && document.visibilityState;
|
||||
|
||||
static get currentState() {
|
||||
if (!AppState.isSupported) {
|
||||
if (!AppState.isAvailable) {
|
||||
return AppState.ACTIVE;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class AppState {
|
||||
}
|
||||
|
||||
static addEventListener(type: string, handler: Function) {
|
||||
if (AppState.isSupported) {
|
||||
if (AppState.isAvailable) {
|
||||
invariant(
|
||||
EVENT_TYPES.indexOf(type) !== -1,
|
||||
'Trying to subscribe to unknown event: "%s"',
|
||||
@@ -44,7 +44,7 @@ class AppState {
|
||||
}
|
||||
|
||||
static removeEventListener(type: string, handler: Function) {
|
||||
if (AppState.isSupported) {
|
||||
if (AppState.isAvailable) {
|
||||
invariant(
|
||||
EVENT_TYPES.indexOf(type) !== -1,
|
||||
'Trying to remove listener for unknown event: "%s"',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apis/AsyncStorage mergeLocalStorageItem should have same behavior as react-native 1`] = `
|
||||
exports[`apis/AsyncStorage mergeItem calls callback after setting item 1`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Chris",
|
||||
@@ -11,3 +11,63 @@ Object {
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/AsyncStorage mergeItem promise after setting item 1`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Chris",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "brown",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/AsyncStorage multiMerge calls callback after setting items 1`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Chris",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "brown",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/AsyncStorage multiMerge calls callback after setting items 2`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Amy",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "black",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/AsyncStorage multiMerge promise after setting items 1`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Chris",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "brown",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/AsyncStorage multiMerge promise after setting items 2`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Amy",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "black",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,77 +1,275 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
import AsyncStorage from '..';
|
||||
|
||||
const waterfall = (fns, cb) => {
|
||||
const _waterfall = (...args) => {
|
||||
const fn = (fns || []).shift();
|
||||
if (typeof fn === 'function') {
|
||||
fn(...args, (err, ...nextArgs) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
} else {
|
||||
return _waterfall(...nextArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(null, ...args);
|
||||
}
|
||||
};
|
||||
_waterfall();
|
||||
};
|
||||
const originalLocalStorage = window.localStorage;
|
||||
|
||||
const obj = {};
|
||||
let obj = {};
|
||||
const mockLocalStorage = {
|
||||
length: 0,
|
||||
clear() {
|
||||
obj = {};
|
||||
mockLocalStorage.length = 0;
|
||||
},
|
||||
getItem(key) {
|
||||
return obj[key];
|
||||
},
|
||||
key(index) {
|
||||
return Object.keys(obj)[index];
|
||||
},
|
||||
removeItem(key) {
|
||||
delete obj[key];
|
||||
mockLocalStorage.length -= 1;
|
||||
},
|
||||
setItem(key, value) {
|
||||
obj[key] = value;
|
||||
mockLocalStorage.length += 1;
|
||||
}
|
||||
};
|
||||
const originalLocalStorage = window.localStorage;
|
||||
|
||||
const uid123Object = {
|
||||
name: 'Chris',
|
||||
age: 30,
|
||||
traits: { hair: 'brown', eyes: 'green' }
|
||||
};
|
||||
const uid123Delta = {
|
||||
age: 31,
|
||||
traits: { eyes: 'blue', shoe_size: 10 }
|
||||
};
|
||||
const uid124Object = {
|
||||
name: 'Amy',
|
||||
age: 28,
|
||||
traits: { hair: 'black', eyes: 'brown' }
|
||||
};
|
||||
|
||||
describe('apis/AsyncStorage', () => {
|
||||
describe('mergeLocalStorageItem', () => {
|
||||
test('should have same behavior as react-native', done => {
|
||||
window.localStorage = mockLocalStorage;
|
||||
// https://facebook.github.io/react-native/docs/asyncstorage.html
|
||||
const UID123_object = {
|
||||
name: 'Chris',
|
||||
age: 30,
|
||||
traits: { hair: 'brown', eyes: 'brown' }
|
||||
};
|
||||
const UID123_delta = {
|
||||
age: 31,
|
||||
traits: { eyes: 'blue', shoe_size: 10 }
|
||||
};
|
||||
beforeEach(() => {
|
||||
mockLocalStorage.setItem('UID123', JSON.stringify(uid123Object));
|
||||
mockLocalStorage.setItem('UID124', JSON.stringify(uid124Object));
|
||||
window.localStorage = mockLocalStorage;
|
||||
});
|
||||
|
||||
waterfall(
|
||||
[
|
||||
cb => {
|
||||
AsyncStorage.setItem('UID123', JSON.stringify(UID123_object))
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
},
|
||||
cb => {
|
||||
AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta))
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
},
|
||||
cb => {
|
||||
AsyncStorage.getItem('UID123')
|
||||
.then(result => {
|
||||
cb(null, JSON.parse(result));
|
||||
})
|
||||
.catch(cb);
|
||||
}
|
||||
],
|
||||
afterEach(() => {
|
||||
mockLocalStorage.clear();
|
||||
window.localStorage = originalLocalStorage;
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
const assertResult = () => {
|
||||
expect(mockLocalStorage.length).toEqual(0);
|
||||
};
|
||||
|
||||
test('promise of erased keys', () => {
|
||||
expect(mockLocalStorage.length).toEqual(2);
|
||||
return AsyncStorage.clear().then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after erasing keys', done => {
|
||||
expect(mockLocalStorage.length).toEqual(2);
|
||||
AsyncStorage.clear(err => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllKeys', () => {
|
||||
const assertResult = result => {
|
||||
expect(result).toEqual(['UID123', 'UID124']);
|
||||
};
|
||||
|
||||
test('promise of keys', () => {
|
||||
return AsyncStorage.getAllKeys().then(result => {
|
||||
assertResult(result);
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback with keys', done => {
|
||||
AsyncStorage.getAllKeys((err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult(result);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getItem', () => {
|
||||
const assertResult = result => {
|
||||
expect(result).toEqual(JSON.stringify(uid123Object));
|
||||
};
|
||||
|
||||
test('promise of item', () => {
|
||||
return AsyncStorage.getItem('UID123').then(result => {
|
||||
assertResult(result);
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback with item', done => {
|
||||
AsyncStorage.getItem('UID123', (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult(result);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiGet', () => {
|
||||
const assertResult = result => {
|
||||
expect(result).toEqual([
|
||||
['UID123', JSON.stringify(uid123Object)],
|
||||
['UID124', JSON.stringify(uid124Object)]
|
||||
]);
|
||||
};
|
||||
|
||||
test('promise of items', () => {
|
||||
return AsyncStorage.multiGet(['UID123', 'UID124']).then(result => {
|
||||
assertResult(result);
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback with items', done => {
|
||||
AsyncStorage.multiGet(['UID123', 'UID124'], (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult(result);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setItem', () => {
|
||||
const assertResult = () => {
|
||||
expect(mockLocalStorage.getItem('UID123')).toEqual(JSON.stringify(uid123Object));
|
||||
};
|
||||
|
||||
test('promise after setting item', () => {
|
||||
return AsyncStorage.setItem('UID123', JSON.stringify(uid123Object)).then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting item', done => {
|
||||
AsyncStorage.setItem('UID123', JSON.stringify(uid123Object), (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiSet', () => {
|
||||
const assertResult = result => {
|
||||
expect(mockLocalStorage.getItem('UID123')).toEqual(JSON.stringify(uid123Object));
|
||||
expect(mockLocalStorage.getItem('UID124')).toEqual(JSON.stringify(uid124Object));
|
||||
};
|
||||
|
||||
test('promise after setting items', () => {
|
||||
return AsyncStorage.multiSet([
|
||||
['UID123', JSON.stringify(uid123Object)],
|
||||
['UID124', JSON.stringify(uid124Object)]
|
||||
]).then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting items', done => {
|
||||
AsyncStorage.multiSet(
|
||||
[['UID123', JSON.stringify(uid123Object)], ['UID124', JSON.stringify(uid124Object)]],
|
||||
(err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
expect(result).toMatchSnapshot();
|
||||
window.localStorage = originalLocalStorage;
|
||||
assertResult();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeItem', () => {
|
||||
const assertResult = () => {
|
||||
expect(JSON.parse(mockLocalStorage.getItem('UID123'))).toMatchSnapshot();
|
||||
};
|
||||
|
||||
test('promise after setting item', () => {
|
||||
return AsyncStorage.mergeItem('UID123', JSON.stringify(uid123Delta)).then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting item', done => {
|
||||
AsyncStorage.mergeItem('UID123', JSON.stringify(uid123Delta), (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiMerge', () => {
|
||||
const assertResult = result => {
|
||||
expect(JSON.parse(mockLocalStorage.getItem('UID123'))).toMatchSnapshot();
|
||||
expect(JSON.parse(mockLocalStorage.getItem('UID124'))).toMatchSnapshot();
|
||||
};
|
||||
|
||||
test('promise after setting items', () => {
|
||||
return AsyncStorage.multiMerge([
|
||||
['UID123', JSON.stringify(uid123Delta)],
|
||||
['UID124', JSON.stringify(uid123Delta)]
|
||||
]).then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting items', done => {
|
||||
AsyncStorage.multiMerge(
|
||||
[['UID123', JSON.stringify(uid123Delta)], ['UID124', JSON.stringify(uid123Delta)]],
|
||||
(err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeItem', () => {
|
||||
const assertResult = () => {
|
||||
expect(mockLocalStorage.getItem('UID123')).toBeUndefined();
|
||||
};
|
||||
|
||||
test('promise after setting item', () => {
|
||||
return AsyncStorage.removeItem('UID123').then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting item', done => {
|
||||
AsyncStorage.removeItem('UID123', (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiRemove', () => {
|
||||
const assertResult = result => {
|
||||
expect(mockLocalStorage.getItem('UID123')).toBeUndefined();
|
||||
expect(mockLocalStorage.getItem('UID124')).toBeUndefined();
|
||||
};
|
||||
|
||||
test('promise after setting items', () => {
|
||||
return AsyncStorage.multiRemove(['UID123', 'UID124']).then(() => {
|
||||
assertResult();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls callback after setting items', done => {
|
||||
AsyncStorage.multiRemove(['UID123', 'UID124'], (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
assertResult();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,66 +13,69 @@ const mergeLocalStorageItem = (key, value) => {
|
||||
window.localStorage.setItem(key, nextValue);
|
||||
};
|
||||
|
||||
const createPromise = (getValue, callback) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const value = getValue();
|
||||
if (callback) {
|
||||
callback(null, value);
|
||||
}
|
||||
resolve(value);
|
||||
} catch (err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createPromiseAll = (promises, callback, processResult) => {
|
||||
return Promise.all(promises).then(
|
||||
result => {
|
||||
const value = processResult ? processResult(result) : null;
|
||||
callback && callback(null, value);
|
||||
return Promise.resolve(value);
|
||||
},
|
||||
errors => {
|
||||
callback && callback(errors);
|
||||
return Promise.reject(errors);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
class AsyncStorage {
|
||||
/**
|
||||
* Erases *all* AsyncStorage for the domain.
|
||||
*/
|
||||
static clear() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.clear();
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
static clear(callback) {
|
||||
return createPromise(() => {
|
||||
window.localStorage.clear();
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets *all* keys known to the app, for all callers, libraries, etc.
|
||||
*/
|
||||
static getAllKeys() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const numberOfKeys = window.localStorage.length;
|
||||
const keys = [];
|
||||
for (let i = 0; i < numberOfKeys; i += 1) {
|
||||
const key = window.localStorage.key(i);
|
||||
keys.push(key);
|
||||
}
|
||||
resolve(keys);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
static getAllKeys(callback) {
|
||||
return createPromise(() => {
|
||||
const numberOfKeys = window.localStorage.length;
|
||||
const keys = [];
|
||||
for (let i = 0; i < numberOfKeys; i += 1) {
|
||||
const key = window.localStorage.key(i);
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches `key` value.
|
||||
*/
|
||||
static getItem(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const value = window.localStorage.getItem(key);
|
||||
resolve(value);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges existing value with input value, assuming they are stringified JSON.
|
||||
*/
|
||||
static mergeItem(key: string, value: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
mergeLocalStorageItem(key, value);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
static getItem(key: string, callback) {
|
||||
return createPromise(() => {
|
||||
return window.localStorage.getItem(key);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,13 +84,37 @@ class AsyncStorage {
|
||||
*
|
||||
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
|
||||
*/
|
||||
static multiGet(keys: Array<string>) {
|
||||
static multiGet(keys: Array<string>, callback) {
|
||||
const promises = keys.map(key => AsyncStorage.getItem(key));
|
||||
const processResult = result => result.map((value, i) => [keys[i], value]);
|
||||
return createPromiseAll(promises, callback, processResult);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(
|
||||
result => Promise.resolve(result.map((value, i) => [keys[i], value])),
|
||||
error => Promise.reject(error)
|
||||
);
|
||||
/**
|
||||
* Sets `value` for `key`.
|
||||
*/
|
||||
static setItem(key: string, value: string, callback) {
|
||||
return createPromise(() => {
|
||||
window.localStorage.setItem(key, value);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of key-value array pairs.
|
||||
* multiSet([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
static multiSet(keyValuePairs: Array<Array<string>>, callback) {
|
||||
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
|
||||
return createPromiseAll(promises, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges existing value with input value, assuming they are stringified JSON.
|
||||
*/
|
||||
static mergeItem(key: string, value: string, callback) {
|
||||
return createPromise(() => {
|
||||
mergeLocalStorageItem(key, value);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,57 +123,26 @@ class AsyncStorage {
|
||||
*
|
||||
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
static multiMerge(keyValuePairs: Array<Array<string>>) {
|
||||
static multiMerge(keyValuePairs: Array<Array<string>>, callback) {
|
||||
const promises = keyValuePairs.map(item => AsyncStorage.mergeItem(item[0], item[1]));
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null), error => Promise.reject(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the keys in the `keys` array.
|
||||
*/
|
||||
static multiRemove(keys: Array<string>) {
|
||||
const promises = keys.map(key => AsyncStorage.removeItem(key));
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null), error => Promise.reject(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of key-value array pairs.
|
||||
* multiSet([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
static multiSet(keyValuePairs: Array<Array<string>>) {
|
||||
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null), error => Promise.reject(error));
|
||||
return createPromiseAll(promises, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a `key`
|
||||
*/
|
||||
static removeItem(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.removeItem(key);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
static removeItem(key: string, callback) {
|
||||
return createPromise(() => {
|
||||
return window.localStorage.removeItem(key);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `value` for `key`.
|
||||
* Delete all the keys in the `keys` array.
|
||||
*/
|
||||
static setItem(key: string, value: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.setItem(key, value);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
static multiRemove(keys: Array<string>, callback) {
|
||||
const promises = keys.map(key => AsyncStorage.removeItem(key));
|
||||
return createPromiseAll(promises, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
/* global window */
|
||||
|
||||
class Clipboard {
|
||||
static isSupported() {
|
||||
return (
|
||||
typeof document.queryCommandSupported === 'function' && document.queryCommandSupported('copy')
|
||||
);
|
||||
}
|
||||
|
||||
static getString() {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
static setString(text) {
|
||||
let success = false;
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = text;
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
|
||||
// add the text to a hidden node
|
||||
const node = document.createElement('span');
|
||||
node.textContent = text;
|
||||
node.style.position = 'absolute';
|
||||
node.style.opacity = '0';
|
||||
document.body.appendChild(node);
|
||||
|
||||
// select the text
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(node);
|
||||
selection.addRange(range);
|
||||
|
||||
// attempt to copy
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
success = true;
|
||||
} catch (e) {}
|
||||
document.body.removeChild(textField);
|
||||
|
||||
// remove selection and node
|
||||
selection.removeAllRanges();
|
||||
document.body.removeChild(node);
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const Linking = {
|
||||
* https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
const iframeOpen = url => {
|
||||
const noOpener = url.indexOf('mailto:') !== 0;
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
@@ -36,7 +37,7 @@ const iframeOpen = url => {
|
||||
const script = iframeDoc.createElement('script');
|
||||
script.text = `
|
||||
window.parent = null; window.top = null; window.frameElement = null;
|
||||
var child = window.open("${url}"); child.opener = null;
|
||||
var child = window.open("${url}"); ${noOpener && 'child.opener = null'};
|
||||
`;
|
||||
iframeDoc.body.appendChild(script);
|
||||
document.body.removeChild(iframe);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ import createReactDOMStyle from './createReactDOMStyle';
|
||||
import flattenArray from '../../modules/flattenArray';
|
||||
import flattenStyle from './flattenStyle';
|
||||
import I18nManager from '../I18nManager';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import prefixInlineStyles from './prefixInlineStyles';
|
||||
import i18nStyle from './i18nStyle';
|
||||
import { prefixInlineStyles } from '../../modules/prefixStyles';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import StyleManager from './StyleManager';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
const createCacheKey = id => {
|
||||
const prefix = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
const prefix = 'rn';
|
||||
return `${prefix}-${id}`;
|
||||
};
|
||||
|
||||
@@ -20,7 +22,7 @@ const classListToString = list => list.join(' ').trim();
|
||||
|
||||
class StyleRegistry {
|
||||
constructor() {
|
||||
this.cache = {};
|
||||
this.cache = { ltr: {}, rtl: {} };
|
||||
this.styleManager = new StyleManager();
|
||||
}
|
||||
|
||||
@@ -33,35 +35,43 @@ class StyleRegistry {
|
||||
*/
|
||||
register(flatStyle) {
|
||||
const id = ReactNativePropRegistry.register(flatStyle);
|
||||
const key = createCacheKey(id);
|
||||
const style = createReactDOMStyle(flatStyle);
|
||||
const classList = mapKeyValue(style, (prop, value) => {
|
||||
if (value != null) {
|
||||
return this.styleManager.setDeclaration(prop, value);
|
||||
}
|
||||
});
|
||||
const className = classList.join(' ').trim();
|
||||
this.cache[key] = { classList, className };
|
||||
this._registerById(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
_registerById(id) {
|
||||
const dir = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
if (!this.cache[dir][id]) {
|
||||
const style = flattenStyle(id);
|
||||
const domStyle = createReactDOMStyle(i18nStyle(style));
|
||||
Object.keys(domStyle).forEach(styleProp => {
|
||||
const value = domStyle[styleProp];
|
||||
if (value != null) {
|
||||
this.styleManager.setDeclaration(styleProp, value);
|
||||
}
|
||||
});
|
||||
this.cache[dir][id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a React Native style object to DOM attributes
|
||||
*/
|
||||
resolve(reactNativeStyle) {
|
||||
resolve(reactNativeStyle, options = emptyObject) {
|
||||
if (!reactNativeStyle) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// fast and cachable
|
||||
if (typeof reactNativeStyle === 'number') {
|
||||
this._registerById(reactNativeStyle);
|
||||
const key = createCacheKey(reactNativeStyle);
|
||||
return this._resolveStyleIfNeeded(key, reactNativeStyle);
|
||||
return this._resolveStyleIfNeeded(reactNativeStyle, options, key);
|
||||
}
|
||||
|
||||
// resolve a plain RN style object
|
||||
if (!Array.isArray(reactNativeStyle)) {
|
||||
return this._resolveStyle(reactNativeStyle);
|
||||
return this._resolveStyle(reactNativeStyle, options);
|
||||
}
|
||||
|
||||
// flatten the style array
|
||||
@@ -70,55 +80,72 @@ class StyleRegistry {
|
||||
const flatArray = flattenArray(reactNativeStyle);
|
||||
let isArrayOfNumbers = true;
|
||||
for (let i = 0; i < flatArray.length; i++) {
|
||||
if (typeof flatArray[i] !== 'number') {
|
||||
const id = flatArray[i];
|
||||
if (typeof id !== 'number') {
|
||||
isArrayOfNumbers = false;
|
||||
break;
|
||||
} else {
|
||||
this._registerById(id);
|
||||
}
|
||||
}
|
||||
const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
|
||||
return this._resolveStyleIfNeeded(key, flatArray);
|
||||
return this._resolveStyleIfNeeded(flatArray, options, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, domStyleProps, options) {
|
||||
const { classList: rdomClassList, style: rdomStyle } = domStyleProps;
|
||||
|
||||
// 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);
|
||||
// Convert the DOM classList back into a React Native form
|
||||
// Preserves unrecognized class names.
|
||||
const { classList: rnClassList, style: rnStyle } = rdomClassList.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: {} }
|
||||
);
|
||||
|
||||
// Create next DOM style props from current and next RN styles
|
||||
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 => {
|
||||
const { prop } = this.styleManager.getDeclaration(className);
|
||||
if (style[prop]) {
|
||||
style[prop] = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve the two React Native styles.
|
||||
const { classList, style = {} } = this.resolve([previousReactNativeStyle, reactNativeStyle]);
|
||||
// Next inline styles take priority over current inline styles
|
||||
Object.assign(style, rdomStyleNext);
|
||||
|
||||
// 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;
|
||||
});
|
||||
// Add the current class names not managed by React Native
|
||||
const className = classListToString(rdomClassListNext.concat(rnClassList));
|
||||
|
||||
classList.push(preservedClassNames);
|
||||
|
||||
const className = classListToString(classList);
|
||||
return { className, style };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
@@ -150,15 +177,16 @@ class StyleRegistry {
|
||||
/**
|
||||
* Caching layer over 'resolveStyle'
|
||||
*/
|
||||
_resolveStyleIfNeeded(key, style) {
|
||||
_resolveStyleIfNeeded(style, options, key) {
|
||||
const dir = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
if (key) {
|
||||
if (!this.cache[key]) {
|
||||
if (!this.cache[dir][key]) {
|
||||
// slow: convert style object to props and cache
|
||||
this.cache[key] = this._resolveStyle(style);
|
||||
this.cache[dir][key] = this._resolveStyle(style, options);
|
||||
}
|
||||
return this.cache[key];
|
||||
return this.cache[dir][key];
|
||||
}
|
||||
return this._resolveStyle(style);
|
||||
return this._resolveStyle(style, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -58,19 +69,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`apis/StyleSheet/StyleRegistry resolve with register before RTL, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-marginRight-zso239",
|
||||
"rn-right-1bnbe1j",
|
||||
"rn-textAlign-1ff274t",
|
||||
],
|
||||
"className": "rn-marginRight-zso239 rn-right-1bnbe1j rn-textAlign-1ff274t",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/StyleRegistry resolve with register, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
@@ -192,35 +203,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,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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,27 +0,0 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import resolveTransform from '../resolveTransform';
|
||||
|
||||
describe('apis/StyleSheet/resolveTransform', () => {
|
||||
test('transform', () => {
|
||||
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,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;
|
||||
|
||||
@@ -43,17 +43,6 @@ const flipProperty = (prop: String): String => {
|
||||
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop;
|
||||
};
|
||||
|
||||
/**
|
||||
* BiDi flip translateX
|
||||
*/
|
||||
const flipTransform = (transform: Object): Object => {
|
||||
const translateX = transform.translateX;
|
||||
if (translateX != null) {
|
||||
transform.translateX = additiveInverse(translateX);
|
||||
}
|
||||
return transform;
|
||||
};
|
||||
|
||||
const swapLeftRight = (value: String): String => {
|
||||
return value === 'left' ? 'right' : value === 'right' ? 'left' : value;
|
||||
};
|
||||
@@ -71,16 +60,16 @@ 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] = style[prop].map(flipTransform);
|
||||
nextStyle[prop] = value;
|
||||
nextStyle[prop].width = additiveInverse(value.width);
|
||||
} else {
|
||||
nextStyle[prop] = style[prop];
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
if (Array.isArray(style.transform)) {
|
||||
const transform = style.transform.map(mapTransform).join(' ');
|
||||
resolvedStyle.transform = transform;
|
||||
} else if (style.transformMatrix) {
|
||||
const transform = convertTransformMatrix(style.transformMatrix);
|
||||
resolvedStyle.transform = transform;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = resolveTransform;
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -143,11 +143,7 @@ const ScrollView = createReactClass({
|
||||
children={this.props.children}
|
||||
collapsable={false}
|
||||
ref={this._setInnerViewRef}
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
horizontal && styles.contentContainerHorizontal,
|
||||
contentContainerStyle
|
||||
]}
|
||||
style={[horizontal && styles.contentContainerHorizontal, contentContainerStyle]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -232,16 +228,17 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
WebkitOverflowScrolling: 'touch'
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
// Enable hardware compositing in modern browsers.
|
||||
// Creates a new layer with its own backing surface that can significantly
|
||||
// improve scroll performance.
|
||||
transform: [{ translateZ: 0 }]
|
||||
},
|
||||
baseHorizontal: {
|
||||
flexDirection: 'row',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
transform: [{ translateZ: 0 }]
|
||||
},
|
||||
contentContainerHorizontal: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
|
||||
14
src/components/StatusBar/index.js
Normal file
14
src/components/StatusBar/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
class StatusBar extends Component {
|
||||
static setBackgroundColor() {}
|
||||
static setBarStyle() {}
|
||||
static setHidden() {}
|
||||
static setNetworkActivityIndicatorVisible() {}
|
||||
static setTranslucent() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StatusBar;
|
||||
@@ -25,6 +25,7 @@ const TextOnlyStylePropTypes = {
|
||||
textShadowRadius: number,
|
||||
writingDirection: WritingDirectionPropType,
|
||||
/* @platform web */
|
||||
textIndent: numberOrString,
|
||||
textOverflow: string,
|
||||
textRendering: oneOf(['auto', 'geometricPrecision', 'optimizeLegibility', 'optimizeSpeed']),
|
||||
textTransform: oneOf(['capitalize', 'lowercase', 'none', 'uppercase']),
|
||||
|
||||
@@ -147,6 +147,7 @@ class TextInput extends Component {
|
||||
style,
|
||||
/* eslint-disable */
|
||||
blurOnSubmit,
|
||||
caretHidden,
|
||||
clearButtonMode,
|
||||
clearTextOnFocus,
|
||||
dataDetectorTypes,
|
||||
@@ -163,6 +164,8 @@ class TextInput extends Component {
|
||||
selection,
|
||||
selectionColor,
|
||||
selectTextOnFocus,
|
||||
textBreakStrategy,
|
||||
underlineColorAndroid,
|
||||
/* eslint-enable */
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = {
|
||||
* @platform unsupported
|
||||
*/
|
||||
elevation: number,
|
||||
/*
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
backgroundAttachment: string,
|
||||
@@ -29,8 +29,11 @@ module.exports = {
|
||||
backgroundRepeat: string,
|
||||
backgroundSize: string,
|
||||
boxShadow: string,
|
||||
clip: string,
|
||||
cursor: string,
|
||||
filter: string,
|
||||
outline: string,
|
||||
outlineColor: ColorPropType,
|
||||
perspective: oneOfType([number, string]),
|
||||
perspectiveOrigin: string,
|
||||
transitionDelay: string,
|
||||
@@ -39,5 +42,6 @@ module.exports = {
|
||||
transitionTimingFunction: string,
|
||||
userSelect: string,
|
||||
willChange: string,
|
||||
WebkitMaskImage: string,
|
||||
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
|
||||
};
|
||||
|
||||
97
src/index.js
97
src/index.js
@@ -1,53 +1,57 @@
|
||||
import createDOMElement from './modules/createDOMElement';
|
||||
import findNodeHandle from './modules/findNodeHandle';
|
||||
import modality from './modules/modality';
|
||||
import NativeModules from './modules/NativeModules';
|
||||
import processColor from './modules/processColor';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import {
|
||||
// top-level API
|
||||
findNodeHandle,
|
||||
render,
|
||||
unmountComponentAtNode,
|
||||
|
||||
// APIs
|
||||
import Animated from './apis/Animated';
|
||||
import AppRegistry from './apis/AppRegistry';
|
||||
import AppState from './apis/AppState';
|
||||
import AsyncStorage from './apis/AsyncStorage';
|
||||
import BackAndroid from './apis/BackAndroid';
|
||||
import Clipboard from './apis/Clipboard';
|
||||
import Dimensions from './apis/Dimensions';
|
||||
import Easing from 'animated/lib/Easing';
|
||||
import I18nManager from './apis/I18nManager';
|
||||
import InteractionManager from './apis/InteractionManager';
|
||||
import Linking from './apis/Linking';
|
||||
import NetInfo from './apis/NetInfo';
|
||||
import PanResponder from './apis/PanResponder';
|
||||
import PixelRatio from './apis/PixelRatio';
|
||||
import Platform from './apis/Platform';
|
||||
import StyleSheet from './apis/StyleSheet';
|
||||
import UIManager from './apis/UIManager';
|
||||
import Vibration from './apis/Vibration';
|
||||
// modules
|
||||
createDOMElement,
|
||||
NativeModules,
|
||||
processColor,
|
||||
|
||||
// components
|
||||
import ActivityIndicator from './components/ActivityIndicator';
|
||||
import Button from './components/Button';
|
||||
import Image from './components/Image';
|
||||
import ListView from './components/ListView';
|
||||
import ProgressBar from './components/ProgressBar';
|
||||
import ScrollView from './components/ScrollView';
|
||||
import Switch from './components/Switch';
|
||||
import Text from './components/Text';
|
||||
import TextInput from './components/TextInput';
|
||||
import Touchable from './components/Touchable/Touchable';
|
||||
import TouchableHighlight from './components/Touchable/TouchableHighlight';
|
||||
import TouchableOpacity from './components/Touchable/TouchableOpacity';
|
||||
import TouchableWithoutFeedback from './components/Touchable/TouchableWithoutFeedback';
|
||||
import View from './components/View';
|
||||
// APIs
|
||||
Animated,
|
||||
AppRegistry,
|
||||
AppState,
|
||||
AsyncStorage,
|
||||
BackAndroid,
|
||||
Clipboard,
|
||||
Dimensions,
|
||||
Easing,
|
||||
I18nManager,
|
||||
InteractionManager,
|
||||
Linking,
|
||||
NetInfo,
|
||||
PanResponder,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
UIManager,
|
||||
Vibration,
|
||||
|
||||
// propTypes
|
||||
import ColorPropType from './propTypes/ColorPropType';
|
||||
import EdgeInsetsPropType from './propTypes/EdgeInsetsPropType';
|
||||
import PointPropType from './propTypes/PointPropType';
|
||||
import ViewPropTypes from './components/View/ViewPropTypes';
|
||||
// components
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Image,
|
||||
ListView,
|
||||
ProgressBar,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
Touchable,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
|
||||
modality();
|
||||
// propTypes
|
||||
ColorPropType,
|
||||
EdgeInsetsPropType,
|
||||
PointPropType,
|
||||
ViewPropTypes
|
||||
} from './module';
|
||||
|
||||
const ReactNative = {
|
||||
// top-level API
|
||||
@@ -87,6 +91,7 @@ const ReactNative = {
|
||||
ListView,
|
||||
ProgressBar,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
|
||||
50
src/module.js
Normal file
50
src/module.js
Normal file
@@ -0,0 +1,50 @@
|
||||
export { default as createDOMElement } from './modules/createDOMElement';
|
||||
export { default as findNodeHandle } from './modules/findNodeHandle';
|
||||
export { default as NativeModules } from './modules/NativeModules';
|
||||
export { default as processColor } from './modules/processColor';
|
||||
export { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
// APIs
|
||||
export { default as Animated } from './apis/Animated';
|
||||
export { default as AppRegistry } from './apis/AppRegistry';
|
||||
export { default as AppState } from './apis/AppState';
|
||||
export { default as AsyncStorage } from './apis/AsyncStorage';
|
||||
export { default as BackAndroid } from './apis/BackAndroid';
|
||||
export { default as Clipboard } from './apis/Clipboard';
|
||||
export { default as Dimensions } from './apis/Dimensions';
|
||||
export { default as Easing } from 'animated/lib/Easing';
|
||||
export { default as I18nManager } from './apis/I18nManager';
|
||||
export { default as InteractionManager } from './apis/InteractionManager';
|
||||
export { default as Linking } from './apis/Linking';
|
||||
export { default as NetInfo } from './apis/NetInfo';
|
||||
export { default as PanResponder } from './apis/PanResponder';
|
||||
export { default as PixelRatio } from './apis/PixelRatio';
|
||||
export { default as Platform } from './apis/Platform';
|
||||
export { default as StyleSheet } from './apis/StyleSheet';
|
||||
export { default as UIManager } from './apis/UIManager';
|
||||
export { default as Vibration } from './apis/Vibration';
|
||||
|
||||
// components
|
||||
export { default as ActivityIndicator } from './components/ActivityIndicator';
|
||||
export { default as Button } from './components/Button';
|
||||
export { default as Image } from './components/Image';
|
||||
export { default as ListView } from './components/ListView';
|
||||
export { default as ProgressBar } from './components/ProgressBar';
|
||||
export { default as ScrollView } from './components/ScrollView';
|
||||
export { default as StatusBar } from './components/StatusBar';
|
||||
export { default as Switch } from './components/Switch';
|
||||
export { default as Text } from './components/Text';
|
||||
export { default as TextInput } from './components/TextInput';
|
||||
export { default as Touchable } from './components/Touchable/Touchable';
|
||||
export { default as TouchableHighlight } from './components/Touchable/TouchableHighlight';
|
||||
export { default as TouchableOpacity } from './components/Touchable/TouchableOpacity';
|
||||
export {
|
||||
default as TouchableWithoutFeedback
|
||||
} from './components/Touchable/TouchableWithoutFeedback';
|
||||
export { default as View } from './components/View';
|
||||
|
||||
// propTypes
|
||||
export { default as ColorPropType } from './propTypes/ColorPropType';
|
||||
export { default as EdgeInsetsPropType } from './propTypes/EdgeInsetsPropType';
|
||||
export { default as PointPropType } from './propTypes/PointPropType';
|
||||
export { default as ViewPropTypes } from './components/View/ViewPropTypes';
|
||||
@@ -6,6 +6,7 @@ const accessibilityComponentTypeToRole = {
|
||||
const accessibilityTraitsToRole = {
|
||||
adjustable: 'slider',
|
||||
button: 'button',
|
||||
header: 'heading',
|
||||
image: 'img',
|
||||
link: 'link',
|
||||
none: 'presentation',
|
||||
|
||||
@@ -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()`.
|
||||
@@ -67,12 +71,26 @@ 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 = [...node.classList];
|
||||
const nodeStyle = node.style;
|
||||
const classList = Array.prototype.slice.call(node.classList);
|
||||
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 };
|
||||
|
||||
const domProps = createDOMProps(nativeProps, style =>
|
||||
StyleRegistry.resolveStateful(style, classList)
|
||||
// Next DOM state
|
||||
const domProps = createDOMProps(i18nStyle(nativeProps), style =>
|
||||
StyleRegistry.resolveStateful(style, domStyleProps, { i18n: false })
|
||||
);
|
||||
UIManager.updateView(node, domProps, this);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -2,9 +2,12 @@ import '../injectResponderEventPlugin';
|
||||
|
||||
import AccessibilityUtil from '../AccessibilityUtil';
|
||||
import createDOMProps from '../createDOMProps';
|
||||
import modality from '../modality';
|
||||
import normalizeNativeEvent from '../normalizeNativeEvent';
|
||||
import React from 'react';
|
||||
|
||||
modality();
|
||||
|
||||
const eventHandlerNames = {
|
||||
onClick: true,
|
||||
onClickCapture: true,
|
||||
|
||||
@@ -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']
|
||||
};
|
||||
@@ -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;
|
||||
@@ -14,7 +14,6 @@ const LayoutPropTypes = {
|
||||
]),
|
||||
alignItems: oneOf(['baseline', 'center', 'flex-end', 'flex-start', 'stretch']),
|
||||
alignSelf: oneOf(['auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch']),
|
||||
aspectRatio: number,
|
||||
backfaceVisibility: hiddenOrVisible,
|
||||
borderWidth: numberOrString,
|
||||
borderBottomWidth: numberOrString,
|
||||
@@ -61,7 +60,26 @@ const LayoutPropTypes = {
|
||||
top: numberOrString,
|
||||
visibility: hiddenOrVisible,
|
||||
width: numberOrString,
|
||||
zIndex: number
|
||||
zIndex: number,
|
||||
/**
|
||||
* @platform unsupported
|
||||
*/
|
||||
aspectRatio: number,
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
gridAutoColumns: string,
|
||||
gridAutoFlow: string,
|
||||
gridAutoRows: string,
|
||||
gridColumnEnd: string,
|
||||
gridColumnGap: string,
|
||||
gridColumnStart: string,
|
||||
gridRowEnd: string,
|
||||
gridRowGap: string,
|
||||
gridRowStart: string,
|
||||
gridTemplateColumns: string,
|
||||
gridTemplateRows: string,
|
||||
gridTemplateAreas: string
|
||||
};
|
||||
|
||||
module.exports = LayoutPropTypes;
|
||||
|
||||
4
src/vendor/setValueForStyles/index.js
vendored
4
src/vendor/setValueForStyles/index.js
vendored
@@ -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
|
||||
}
|
||||
|
||||
30
yarn.lock
30
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user