Compare commits

..

11 Commits

Author SHA1 Message Date
Nicolas Gallagher
a6f8c111af Preact benchmarks 2017-04-22 11:45:56 -07:00
Nicolas Gallagher
227971d22c [add] support for CSS grid properties (experimental)
Allow people to experiment with using CSS grid in react-native. (No
support for shorthand properties.)
2017-04-22 10:34:43 -07:00
Nicolas Gallagher
4822cf4620 [add] support for 'clip' and 'textIndent' styles 2017-04-22 10:06:27 -07:00
Nathan Broadbent
91e4528eac Allow filter property in CSS 2017-04-22 22:56:07 +07:00
Nicolas Gallagher
1ee64d8285 0.0.86 2017-04-21 19:04:23 -07:00
Nicolas Gallagher
66a4c13bf3 [fix] AppState.isSupported -> AppState.isAvailable
React Native exposes AppState support via 'isAvailable'.
2017-04-21 18:47:29 -07:00
Nicolas Gallagher
9012e98ba7 [fix] support 'mailto:' URLs in 'Linking' 2017-04-21 18:29:29 -07:00
Nicolas Gallagher
046e01dfa9 [fix] add AsyncStorage callbacks and tests
Add support for the callback interface and add test coverage.

Fix #399
Close #400
2017-04-21 18:13:14 -07:00
Nicolas Gallagher
6e71e1e058 [fix] attempt to avoid need for 'Array.from' polyfill
Fix #409
2017-04-20 18:04:09 -07:00
Nicolas Gallagher
d5a9f3e779 Add ES module export
Preparation for publishing an ES module build.
Move 'modality' into 'createDOMElement' to ensure it is always initialized.
2017-04-20 17:16:05 -07:00
Nicolas Gallagher
f16f5f21ce [add] WebkitMaskImage style prop
Undocumented supported. Commonly used in border-radius hacks.

Close #326
2017-04-20 15:12:07 -07:00
22 changed files with 620 additions and 211 deletions

View File

@@ -85,6 +85,7 @@ When `false`, the text is not selectable.
+ `textAlign`
+ `textAlignVertical`
+ `textDecorationLine`
+ `textIndent`
+ `textOverflow`
+ `textRendering`
+ `textShadowColor`

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.85",
"version": "0.0.86",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [

View File

@@ -20,15 +20,21 @@ simple `View` without much of React Native's functionality.
Typical render timings*: mean ± two standard deviations
| Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
| React Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
| :--- | ---: | ---: | ---: |
| `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` |
| Preact Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
| :--- | ---: | ---: | ---: |
| `css-modules` | `744.47` `±284.78` | `2006.23` `±214.00` | |
| `react-native-web/stylesheet@0.0.86` | `1109.43` `±126.15` | `2677.19` `±301.84` | |
| `react-native-web@0.0.86` | `1601.87` `±207.56` | `3963.18` `±396.18` | `17.11` `±7.90ms` |
Other libraries
| Implementation | Deep tree (ms) | Wide tree (ms) |
| React Implementation | Deep tree (ms) | Wide tree (ms) |
| :--- | ---: | ---: |
| `styletron@2.5.1` | `88.48` `±12.00` | `171.89` `±13.28` |
| `aphrodite@1.2.0` | `101.32` `±20.33` | `220.33` `±31.41` |

View File

@@ -6,7 +6,7 @@ import reactNative from './src/react-native';
import reactNativeStyleSheet from './src/react-native-stylesheet';
import styledComponents from './src/styled-components';
import styletron from './src/styletron';
import xp from './src/reactxp';
// import xp from './src/reactxp';
import renderDeepTree from './tests/renderDeepTree';
import renderTweet from './tests/renderTweet';
@@ -37,8 +37,8 @@ const extraTests = [
() => renderWideTree('glamor', glamor),
() => renderDeepTree('react-jss', jss),
() => renderWideTree('react-jss', jss),
() => renderDeepTree('reactxp', xp),
() => renderWideTree('reactxp', xp),
// () => renderDeepTree('reactxp', xp),
// () => renderWideTree('reactxp', xp),
() => renderDeepTree('styled-components', styledComponents),
() => renderWideTree('styled-components', styledComponents)
];

View File

@@ -6,6 +6,8 @@
"classnames": "^2.2.5",
"glamor": "3.0.0-1",
"marky": "^1.2.0",
"preact": "^8.1.0",
"preact-compat": "^3.15.0",
"react-jss": "^6.1.1",
"reactxp": "^0.34.3",
"styled-components": "2.0.0-15",

View File

@@ -49,6 +49,8 @@ module.exports = {
],
resolve: {
alias: {
react: path.join(__dirname, 'node_modules/preact-compat/dist/preact-compat.js'),
'react-dom': path.join(__dirname, 'node_modules/preact-compat/dist/preact-compat.js'),
'react-native': path.join(__dirname, '../src')
}
}

View File

@@ -400,6 +400,12 @@ ifvisible.js@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/ifvisible.js/-/ifvisible.js-1.0.6.tgz#52eb151ce89c56f15316226462e892d1f8451261"
immutability-helper@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.1.2.tgz#734506440d7209b74664dcadaa8ba14e73f2185b"
dependencies:
invariant "^2.2.0"
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -422,6 +428,12 @@ inline-style-prefixer@^3.0.1:
bowser "^1.6.0"
css-in-js-utils "^1.0.3"
invariant@^2.2.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
loose-envify "^1.0.0"
is-absolute-url@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@@ -871,10 +883,38 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
source-map "^0.5.6"
supports-color "^3.2.3"
preact-compat@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/preact-compat/-/preact-compat-3.15.0.tgz#b7818a3d0b20a8c9ca7c65ade0a60b01376902c1"
dependencies:
immutability-helper "^2.1.2"
preact-render-to-string "^3.6.0"
preact-transition-group "^1.1.0"
prop-types "^15.5.8"
standalone-react-addons-pure-render-mixin "^0.1.1"
preact-render-to-string@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-3.6.0.tgz#03a49d2d755a766c3d421e8b06d6edbb33ed6bde"
dependencies:
pretty-format "^3.5.1"
preact-transition-group@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/preact-transition-group/-/preact-transition-group-1.1.1.tgz#f0a49327ea515ece34ea2be864c4a7d29e5d6e10"
preact@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/preact/-/preact-8.1.0.tgz#f035b808eebb74e46d56246b02ca0f190b6d6574"
prepend-http@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
pretty-format@^3.5.1:
version "3.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
promise@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
@@ -994,6 +1034,10 @@ sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
standalone-react-addons-pure-render-mixin@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/standalone-react-addons-pure-render-mixin/-/standalone-react-addons-pure-render-mixin-0.1.1.tgz#3c7409f4c79c40de9ac72c616cf679a994f37551"
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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']),

View File

@@ -18,7 +18,7 @@ module.exports = {
* @platform unsupported
*/
elevation: number,
/*
/**
* @platform web
*/
backgroundAttachment: string,
@@ -29,7 +29,9 @@ module.exports = {
backgroundRepeat: string,
backgroundSize: string,
boxShadow: string,
clip: string,
cursor: string,
filter: string,
outline: string,
outlineColor: ColorPropType,
perspective: oneOfType([number, string]),
@@ -40,5 +42,6 @@ module.exports = {
transitionTimingFunction: string,
userSelect: string,
willChange: string,
WebkitMaskImage: string,
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
};

View File

@@ -1,54 +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 StatusBar from './components/StatusBar';
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

50
src/module.js Normal file
View 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';

View File

@@ -69,7 +69,7 @@ const NativeMethodsMixin = {
setNativeProps(nativeProps: Object) {
// DOM state
const node = findNodeHandle(this);
const classList = [...node.classList];
const classList = Array.prototype.slice.call(node.classList);
const domProps = createDOMProps(nativeProps, style =>
StyleRegistry.resolveStateful(style, classList)

View File

@@ -1,10 +1,13 @@
import '../injectResponderEventPlugin';
// 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,

View File

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