├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── mock.js ├── package.json ├── src ├── Libraries │ ├── EventEmitter │ │ ├── EmitterSubscription.js │ │ ├── EventEmitter.js │ │ ├── EventSubscription.js │ │ ├── EventSubscriptionVendor.js │ │ └── NativeEventEmitter.js │ ├── NavigationExperimental │ │ ├── NavigationCard.js │ │ ├── NavigationPropTypes.js │ │ ├── NavigationStateUtils.js │ │ └── index.js │ └── Network │ │ ├── FormData.js │ │ ├── Headers.js │ │ ├── Response.js │ │ └── XMLHttpRequest.js ├── NativeModules │ ├── ActionSheetManager.js │ ├── AlertManager.js │ ├── AppState.js │ ├── CameraRollManager.js │ ├── Clipboard.js │ ├── DatePickerAndroid.js │ ├── DeviceEventManager.js │ ├── ImagePickerIOS.js │ ├── LinkingManager.js │ ├── ScrollViewManager.js │ ├── SourceCode.js │ ├── TestModule.js │ ├── TimePickerAndroid.js │ ├── Timing.js │ ├── UIManager.js │ ├── Vibration.js │ ├── WebViewManager.js │ └── index.js ├── api │ ├── ActionSheetIOS.js │ ├── Alert.js │ ├── AlertIOS.js │ ├── Animated │ │ ├── AnimatedImplementation.js │ │ ├── Easing.js │ │ ├── Interpolation.js │ │ ├── SpringConfig.js │ │ ├── createAnimatedComponent.js │ │ └── index.js │ ├── AppRegistry.js │ ├── AppState.js │ ├── AppStateIOS.js │ ├── AsyncStorage.js │ ├── BackAndroid.js │ ├── CameraRoll.js │ ├── DatePickerAndroid.js │ ├── Dimensions.js │ ├── ImagePickerIOS.js │ ├── IntentAndroid.js │ ├── InteractionManager.js │ ├── Keyboard.js │ ├── LayoutAnimation.js │ ├── Linking.js │ ├── LinkingIOS.js │ ├── ListViewDataSource.js │ ├── NetInfo.js │ ├── PanResponder.js │ ├── PixelRatio.js │ ├── PushNotificationIOS.js │ ├── Settings.js │ ├── Share.js │ ├── StatusBarIOS.js │ ├── StyleSheet.js │ ├── TextInputState.js │ ├── TimePickerAndroid.js │ ├── TouchHistoryMath.js │ └── VibrationIOS.js ├── components │ ├── ART │ │ ├── Path.js │ │ ├── Transform.js │ │ └── index.js │ ├── ActivityIndicator.js │ ├── ActivityIndicatorIOS.js │ ├── DrawerLayoutAndroid.js │ ├── Image.js │ ├── ListView.js │ ├── Navigator.js │ ├── Picker.js │ ├── ScrollView.js │ ├── StatusBar.js │ ├── TabBarIOS.js │ ├── Text.js │ ├── TextInput.js │ ├── TouchableNativeFeedback.js │ ├── TouchableOpacity.js │ ├── TouchableWithoutFeedback.js │ ├── View.js │ ├── ViewAccessibility.js │ ├── WebView.js │ └── createMockComponent.js ├── defineGlobalProperty.js ├── mixins │ ├── NativeMethodsMixin.js │ ├── ScrollResponder.js │ └── Subscribable.js ├── plugins │ ├── DeviceEventEmitter.js │ ├── NativeAppEventEmitter.js │ ├── Platform.js │ ├── processColor.js │ └── requireNativeComponent.js ├── propTypes │ ├── ColorPropType.js │ ├── EdgeInsetsPropType.js │ ├── ImageResizeMode.js │ ├── ImageStylePropTypes.js │ ├── LayoutPropTypes.js │ ├── PointPropType.js │ ├── ShadowPropTypesIOS.js │ ├── StyleSheetPropType.js │ ├── TextStylePropTypes.js │ ├── TransformPropTypes.js │ ├── ViewPropTypes.js │ ├── ViewStylePropTypes.js │ └── flattenStyle.js ├── react-native.js └── requireLibrary.js └── test ├── StyleSheet.js ├── basic-test.js └── components ├── DrawerLayoutAndroid.js ├── Picker.js └── TabBarIOS.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["airbnb", "react-native"], 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react", 5 | "react-native" 6 | ], 7 | "env": { 8 | "mocha": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "no-unused-vars": [2, {"vars": "all", "args": "none"}], 13 | "comma-dangle": 0, 14 | "func-names": 0, 15 | "prefer-arrow-callback": 0, 16 | "global-require": 0, 17 | "react/prefer-es6-class": 0, 18 | "no-underscore-dangle": 0, 19 | "prefer-rest-params": 0, 20 | "guard-for-in": 0, 21 | "no-restricted-syntax": 0, 22 | "prefer-template": 0, 23 | "no-console": 0, 24 | "max-len": [ 25 | 2, 26 | 120, 27 | 2, 28 | { 29 | "ignoreComments": true 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | /build 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | CONTRIBUTING.md 3 | .eslintrc 4 | .gitignore 5 | test/ 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.0.0" 4 | - "6.0.0" 5 | - "4" 6 | - "6" 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## API 4 | The API should be as close to the API of `react-native` as possible. If there are custom functions or variables exposed in a mock that isn't in the real API should be prefixed with 2 undercores, `__`. Requirement of parameters should be handled in the same way, if a parameter is required in `react-native`, it should be required in `react-native mock`, if it's not needed, then it shouldn't be needed here either. 5 | 6 | ## Components 7 | "leaf node" components can be mocked out as just components returning `null` in their render method, and tested at a higher level using `shallow` from enzyme. Compound RN components like ListView, NavigationExperimental, etc. can do more complex things. 8 | 9 | ## Updates 10 | `react-native-mock` should be compatible with the most recent version of `react-native`. Backwards compatibility is not something that needs to be considered, if the API changes, then so does the mock! 11 | 12 | ## Incrementing the version number 13 | In simple: __Don't__. Leave that for us to manage when deploying! 14 | 15 | ## Tests 16 | When adding a new mock, make sure it's got some in-depth tests, and that the linter passes. PRs with failing tests or tests that aren't in-depth enough won't be merged. This keeps the mock as stable as possible. 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jake Howard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _Interested in helping maintain `react-native-mock`? [Reach out!](https://github.com/RealOrangeOne/react-native-mock/issues/168)_ 2 | 3 | # react-native-mock [![Build Status](https://travis-ci.org/RealOrangeOne/react-native-mock.svg?branch=master)](https://travis-ci.org/RealOrangeOne/react-native-mock) 4 | 5 | [![Join the chat at https://gitter.im/RealOrangeOne/react-native-mock](https://badges.gitter.im/RealOrangeOne/react-native-mock.svg)](https://gitter.im/RealOrangeOne/react-native-mock?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | A fully mocked and test-friendly version of react native 7 | 8 | ## Requirements 9 | - Node.js 4+ 10 | - The latest version of react-native 11 | 12 | __Note__: This library is designed to work with the most recent version of react-native. If you aren't using the most recent version, you will need to download an older version of this library, as the API is likely to be different, and the dependencies are likely to break. 13 | 14 | ## How Am I Supposed To Use This? 15 | 16 | ```bash 17 | npm i react-native-mock --save-dev 18 | ``` 19 | 20 | ```js 21 | /* file-that-runs-before-all-of-my-tests.js */ 22 | 23 | // This will mutate `react-native`'s require cache with `react-native-mock`'s. 24 | require('react-native-mock/mock'); // <-- side-effects!!! 25 | ``` 26 | 27 | ## Why? 28 | 29 | Testing React Native components is *hard*. I'm hoping this makes it easier. 30 | 31 | I wrote a React Testing Library that works really well for React "Web", but didn't really work for React "Native" without something like this. 32 | 33 | 34 | ## Wait... Is this actually a terrible idea? 35 | 36 | I don't know. Maybe. 37 | 38 | I'd love to figure that out though... feel free to file an issue if you have opinions. 39 | 40 | 41 | ## Contributing 42 | Discovered a bug, got a new feature, or found something that needs improving? __Submit a PR!__ 43 | 44 | Make sure to read through the CONTRIBUTING.md file before submitting your PR! 45 | 46 | ### Core Contributors 47 | - [Jake Howard](https://github.com/RealOrangeOne) 48 | - [Leland Richardson](https://github.com/lelandrichardson) (Original Creator) 49 | 50 | ## What do the labels mean? 51 | See [this wiki page](https://github.com/RealOrangeOne/react-native-mock/wiki/Labels---What-do-they-mean%3F). 52 | -------------------------------------------------------------------------------- /mock.js: -------------------------------------------------------------------------------- 1 | const ReactNativeMock = require('./build/react-native'); 2 | 3 | // the cache key that real react native would get 4 | const key = require.resolve('react-native'); 5 | 6 | // make sure the cache is filled with our lib 7 | require.cache[key] = { 8 | id: key, 9 | filename: key, 10 | loaded: true, 11 | exports: ReactNativeMock, 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-mock", 3 | "version": "0.3.1", 4 | "description": "A fully mocked and test-friendly version of react native", 5 | "main": "build/react-native.js", 6 | "scripts": { 7 | "prepublish": "npm run build", 8 | "test": "npm run lint && npm run mocha", 9 | "mocha": "mocha --require babel-core/register 'test/**/*.js'", 10 | "mocha:watch": "npm run test -- --watch", 11 | "build": "rm -rf build/ && babel src --out-dir build", 12 | "lint": "eslint 'src/' 'test/' 'mock.js'" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/RealOrangeOne/react-native-mock.git" 17 | }, 18 | "keywords": [ 19 | "react", 20 | "react-native", 21 | "mock", 22 | "testing" 23 | ], 24 | "author": "Jake Howard ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/RealOrangeOne/react-native-mock/issues" 28 | }, 29 | "homepage": "https://github.com/RealOrangeOne/react-native-mock#readme", 30 | "devDependencies": { 31 | "babel-cli": "6.9.0", 32 | "babel-core": "6.9.0", 33 | "babel-eslint": "6.0.4", 34 | "babel-preset-airbnb": "2.0.0", 35 | "babel-preset-react-native": "1.8.0", 36 | "chai": "3.5.0", 37 | "eslint": "2.10.2", 38 | "eslint-config-airbnb": "9.0.1", 39 | "eslint-plugin-import": "1.8.0", 40 | "eslint-plugin-jsx-a11y": "1.2.2", 41 | "eslint-plugin-react": "5.1.1", 42 | "eslint-plugin-react-native": "1.0.2", 43 | "mocha": "2.5.3", 44 | "react": "^15.4.0", 45 | "react-native": "^0.38.0" 46 | }, 47 | "dependencies": { 48 | "cubic-bezier": "^0.1.2", 49 | "invariant": "^2.2.1", 50 | "keymirror": "^0.1.1", 51 | "raf": "^3.2.0", 52 | "react-addons-create-fragment": "^15.4.0", 53 | "react-addons-perf": "^15.4.0", 54 | "react-addons-pure-render-mixin": "^15.4.0", 55 | "react-addons-test-utils": "^15.4.0", 56 | "react-addons-update": "^15.4.0", 57 | "react-dom": "^15.4.0", 58 | "react-timer-mixin": "^0.13.3", 59 | "warning": "^2.1.0" 60 | }, 61 | "peerDependencies": { 62 | "react": "*", 63 | "react-native": "*" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Libraries/EventEmitter/EmitterSubscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | const EventSubscription = require('./EventSubscription'); 11 | 12 | /** 13 | * EmitterSubscription represents a subscription with listener and context data. 14 | */ 15 | class EmitterSubscription extends EventSubscription { 16 | 17 | /** 18 | * @param {EventEmitter} emitter - The event emitter that registered this 19 | * subscription 20 | * @param {EventSubscriptionVendor} subscriber - The subscriber that controls 21 | * this subscription 22 | * @param {function} listener - Function to invoke when the specified event is 23 | * emitted 24 | * @param {*} context - Optional context object to use when invoking the 25 | * listener 26 | */ 27 | constructor(emitter, subscriber, listener, context) { 28 | super(subscriber); 29 | this.emitter = emitter; 30 | this.listener = listener; 31 | this.context = context; 32 | } 33 | 34 | /** 35 | * Removes this subscription from the emitter that registered it. 36 | * Note: we're overriding the `remove()` method of EventSubscription here 37 | * but deliberately not calling `super.remove()` as the responsibility 38 | * for removing the subscription lies with the EventEmitter. 39 | */ 40 | remove() { 41 | this.emitter.removeSubscription(this); 42 | } 43 | } 44 | 45 | module.exports = EmitterSubscription; 46 | -------------------------------------------------------------------------------- /src/Libraries/EventEmitter/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | const EmitterSubscription = require('./EmitterSubscription'); 11 | const EventSubscriptionVendor = require('./EventSubscriptionVendor'); 12 | const invariant = require('invariant'); 13 | 14 | /** 15 | * @class EventEmitter 16 | * @description 17 | * An EventEmitter is responsible for managing a set of listeners and publishing 18 | * events to them when it is told that such events happened. In addition to the 19 | * data for the given event it also sends a event control object which allows 20 | * the listeners/handlers to prevent the default behavior of the given event. 21 | * 22 | * The emitter is designed to be generic enough to support all the different 23 | * contexts in which one might want to emit events. It is a simple multicast 24 | * mechanism on top of which extra functionality can be composed. For example, a 25 | * more advanced emitter may use an EventHolder and EventFactory. 26 | */ 27 | class EventEmitter { 28 | /** 29 | * @constructor 30 | * 31 | * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance 32 | * to use. If omitted, a new subscriber will be created for the emitter. 33 | */ 34 | constructor(subscriber) { 35 | this._subscriber = subscriber || new EventSubscriptionVendor(); 36 | } 37 | 38 | /** 39 | * Adds a listener to be invoked when events of the specified type are 40 | * emitted. An optional calling context may be provided. The data arguments 41 | * emitted will be passed to the listener function. 42 | * 43 | * TODO: Annotate the listener arg's type. This is tricky because listeners 44 | * can be invoked with varargs. 45 | * 46 | * @param {string} eventType - Name of the event to listen to 47 | * @param {function} listener - Function to invoke when the specified event is 48 | * emitted 49 | * @param {*} context - Optional context object to use when invoking the 50 | * listener 51 | */ 52 | addListener(eventType, listener, context) { 53 | return (this._subscriber.addSubscription( 54 | eventType, 55 | new EmitterSubscription(this, this._subscriber, listener, context) 56 | )); 57 | } 58 | 59 | /** 60 | * Similar to addListener, except that the listener is removed after it is 61 | * invoked once. 62 | * 63 | * @param {string} eventType - Name of the event to listen to 64 | * @param {function} listener - Function to invoke only once when the 65 | * specified event is emitted 66 | * @param {*} context - Optional context object to use when invoking the 67 | * listener 68 | */ 69 | once(eventType, listener, context) { 70 | return this.addListener(eventType, (...args) => { 71 | this.removeCurrentListener(); 72 | listener.apply(context, args); 73 | }); 74 | } 75 | 76 | /** 77 | * Removes all of the registered listeners, including those registered as 78 | * listener maps. 79 | * 80 | * @param {?string} eventType - Optional name of the event whose registered 81 | * listeners to remove 82 | */ 83 | removeAllListeners(eventType) { 84 | this._subscriber.removeAllSubscriptions(eventType); 85 | } 86 | 87 | /** 88 | * Provides an API that can be called during an eventing cycle to remove the 89 | * last listener that was invoked. This allows a developer to provide an event 90 | * object that can remove the listener (or listener map) during the 91 | * invocation. 92 | * 93 | * If it is called when not inside of an emitting cycle it will throw. 94 | * 95 | * @throws {Error} When called not during an eventing cycle 96 | * 97 | * @example 98 | * var subscription = emitter.addListenerMap({ 99 | * someEvent: function(data, event) { 100 | * console.log(data); 101 | * emitter.removeCurrentListener(); 102 | * } 103 | * }); 104 | * 105 | * emitter.emit('someEvent', 'abc'); // logs 'abc' 106 | * emitter.emit('someEvent', 'def'); // does not log anything 107 | */ 108 | removeCurrentListener() { 109 | invariant( 110 | !!this._currentSubscription, 111 | 'Not in an emitting cycle; there is no current subscription' 112 | ); 113 | this.removeSubscription(this._currentSubscription); 114 | } 115 | 116 | /** 117 | * Removes a specific subscription. Called by the `remove()` method of the 118 | * subscription itself to ensure any necessary cleanup is performed. 119 | */ 120 | removeSubscription(subscription) { 121 | invariant( 122 | subscription.emitter === this, 123 | 'Subscription does not belong to this emitter.' 124 | ); 125 | this._subscriber.removeSubscription(subscription); 126 | } 127 | 128 | /** 129 | * Returns an array of listeners that are currently registered for the given 130 | * event. 131 | * 132 | * @param {string} eventType - Name of the event to query 133 | * @returns {array} 134 | */ 135 | listeners(eventType) { 136 | const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); 137 | return subscriptions ? subscriptions.map(subscription => subscription.listener) : []; 138 | } 139 | 140 | /** 141 | * Emits an event of the given type with the given data. All handlers of that 142 | * particular type will be notified. 143 | * 144 | * @param {string} eventType - Name of the event to emit 145 | * @param {...*} Arbitrary arguments to be passed to each registered listener 146 | * 147 | * @example 148 | * emitter.addListener('someEvent', function(message) { 149 | * console.log(message); 150 | * }); 151 | * 152 | * emitter.emit('someEvent', 'abc'); // logs 'abc' 153 | */ 154 | emit(eventType) { 155 | const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); 156 | if (subscriptions) { 157 | for (let i = 0, l = subscriptions.length; i < l; i++) { 158 | const subscription = subscriptions[i]; 159 | 160 | // The subscription may have been removed during this event loop. 161 | if (subscription) { 162 | this._currentSubscription = subscription; 163 | subscription.listener.apply( 164 | subscription.context, 165 | Array.prototype.slice.call(arguments, 1) 166 | ); 167 | } 168 | } 169 | this._currentSubscription = null; 170 | } 171 | } 172 | 173 | /** 174 | * Removes the given listener for event of specific type. 175 | * 176 | * @param {string} eventType - Name of the event to emit 177 | * @param {function} listener - Function to invoke when the specified event is 178 | * emitted 179 | * 180 | * @example 181 | * emitter.removeListener('someEvent', function(message) { 182 | * console.log(message); 183 | * }); // removes the listener if already registered 184 | * 185 | */ 186 | removeListener(eventType, listener) { 187 | const subscriptions = (this._subscriber.getSubscriptionsForType(eventType)); 188 | if (subscriptions) { 189 | for (let i = 0, l = subscriptions.length; i < l; i++) { 190 | const subscription = subscriptions[i]; 191 | 192 | // The subscription may have been removed during this event loop. 193 | // its listener matches the listener in method parameters 194 | if (subscription && subscription.listener === listener) { 195 | subscription.remove(); 196 | } 197 | } 198 | } 199 | } 200 | } 201 | 202 | module.exports = EventEmitter; 203 | -------------------------------------------------------------------------------- /src/Libraries/EventEmitter/EventSubscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | /** 11 | * EventSubscription represents a subscription to a particular event. It can 12 | * remove its own subscription. 13 | */ 14 | class EventSubscription { 15 | /** 16 | * @param {EventSubscriptionVendor} subscriber the subscriber that controls 17 | * this subscription. 18 | */ 19 | constructor(subscriber) { 20 | this.subscriber = subscriber; 21 | } 22 | 23 | /** 24 | * Removes this subscription from the subscriber that controls it. 25 | */ 26 | remove() { 27 | this.subscriber.removeSubscription(this); 28 | } 29 | } 30 | 31 | module.exports = EventSubscription; 32 | -------------------------------------------------------------------------------- /src/Libraries/EventEmitter/EventSubscriptionVendor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | const invariant = require('invariant'); 11 | 12 | /** 13 | * EventSubscriptionVendor stores a set of EventSubscriptions that are 14 | * subscribed to a particular event type. 15 | */ 16 | class EventSubscriptionVendor { 17 | 18 | constructor() { 19 | this._subscriptionsForType = {}; 20 | this._currentSubscription = null; 21 | } 22 | 23 | /** 24 | * Adds a subscription keyed by an event type. 25 | * 26 | * @param {string} eventType 27 | * @param {EventSubscription} subscription 28 | */ 29 | addSubscription(eventType, subscription) { 30 | /* eslint-disable no-param-reassign */ 31 | invariant( 32 | subscription.subscriber === this, 33 | 'The subscriber of the subscription is incorrectly set.'); 34 | if (!this._subscriptionsForType[eventType]) { 35 | this._subscriptionsForType[eventType] = []; 36 | } 37 | const key = this._subscriptionsForType[eventType].length; 38 | this._subscriptionsForType[eventType].push(subscription); 39 | subscription.eventType = eventType; 40 | subscription.key = key; 41 | return subscription; 42 | } 43 | 44 | /** 45 | * Removes a bulk set of the subscriptions. 46 | * 47 | * @param {?string} eventType - Optional name of the event type whose 48 | * registered supscriptions to remove, if null remove all subscriptions. 49 | */ 50 | removeAllSubscriptions(eventType) { 51 | if (eventType === undefined) { 52 | this._subscriptionsForType = {}; 53 | } else { 54 | delete this._subscriptionsForType[eventType]; 55 | } 56 | } 57 | 58 | /** 59 | * Removes a specific subscription. Instead of calling this function, call 60 | * `subscription.remove()` directly. 61 | * 62 | * @param {object} subscription 63 | */ 64 | removeSubscription(subscription) { 65 | const eventType = subscription.eventType; 66 | const key = subscription.key; 67 | 68 | const subscriptionsForType = this._subscriptionsForType[eventType]; 69 | if (subscriptionsForType) { 70 | delete subscriptionsForType[key]; 71 | } 72 | } 73 | 74 | /** 75 | * Returns the array of subscriptions that are currently registered for the 76 | * given event type. 77 | * 78 | * Note: This array can be potentially sparse as subscriptions are deleted 79 | * from it when they are removed. 80 | * 81 | * TODO: This returns a nullable array. wat? 82 | * 83 | * @param {string} eventType 84 | * @returns {?array} 85 | */ 86 | getSubscriptionsForType(eventType) { 87 | return this._subscriptionsForType[eventType]; 88 | } 89 | } 90 | 91 | module.exports = EventSubscriptionVendor; 92 | -------------------------------------------------------------------------------- /src/Libraries/EventEmitter/NativeEventEmitter.js: -------------------------------------------------------------------------------- 1 | const invariant = require('invariant'); 2 | const EventEmitter = require('./EventEmitter'); 3 | const EmitterSubscription = require('./EmitterSubscription'); 4 | const EventSubscriptionVender = require('./EventSubscriptionVendor'); 5 | 6 | const sharedSubscriber = new EventSubscriptionVender(); 7 | 8 | class NativeEventEmitter extends EventEmitter { 9 | constructor(nativeModule) { 10 | super(sharedSubscriber); 11 | invariant(nativeModule, 'Native module cannot be null.'); 12 | this._nativeModule = nativeModule; 13 | } 14 | 15 | addListener(eventType, listener, context) { 16 | return super.addListener(eventType, listener, context); 17 | } 18 | 19 | removeAllListeners(eventType: string) { 20 | invariant(eventType, 'eventType argument is required.'); 21 | super.removeAllListeners(eventType); 22 | } 23 | 24 | removeSubscription(subscription: EmitterSubscription) { 25 | super.removeSubscription(subscription); 26 | } 27 | } 28 | 29 | module.exports = NativeEventEmitter; 30 | -------------------------------------------------------------------------------- /src/Libraries/NavigationExperimental/NavigationCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class CardStackPanResponder { 4 | } 5 | 6 | class PagerPanResponder { 7 | } 8 | 9 | class NavigationCard extends React.Component { 10 | static CardStackPanResponder = CardStackPanResponder; 11 | static CardStackStyleInterpolator = { 12 | forHorizontal: () => ({}), 13 | forVertical: () => ({}), 14 | }; 15 | static PagerPanResponder = PagerPanResponder; 16 | static PagerStyleInterpolator = { 17 | forHorizontal: () => ({}), 18 | }; 19 | } 20 | 21 | module.exports = NavigationCard; 22 | -------------------------------------------------------------------------------- /src/Libraries/NavigationExperimental/NavigationPropTypes.js: -------------------------------------------------------------------------------- 1 | class Animated {} 2 | 3 | import React from 'react'; 4 | 5 | const { PropTypes } = React; 6 | 7 | /* NavigationAction */ 8 | const action = PropTypes.shape({ 9 | type: PropTypes.string.isRequired, 10 | }); 11 | 12 | /* NavigationAnimatedValue */ 13 | const animatedValue = PropTypes.instanceOf(Animated.Value); 14 | 15 | /* NavigationRoute */ 16 | const navigationRoute = PropTypes.shape({ 17 | key: PropTypes.string.isRequired, 18 | }); 19 | 20 | /* navigationRoute */ 21 | const navigationState = PropTypes.shape({ 22 | index: PropTypes.number.isRequired, 23 | routes: PropTypes.arrayOf(navigationRoute), 24 | }); 25 | 26 | /* NavigationLayout */ 27 | const layout = PropTypes.shape({ 28 | height: animatedValue, 29 | initHeight: PropTypes.number.isRequired, 30 | initWidth: PropTypes.number.isRequired, 31 | isMeasured: PropTypes.bool.isRequired, 32 | width: animatedValue, 33 | }); 34 | 35 | /* NavigationScene */ 36 | const scene = PropTypes.shape({ 37 | index: PropTypes.number.isRequired, 38 | isStale: PropTypes.bool.isRequired, 39 | key: PropTypes.string.isRequired, 40 | route: navigationRoute.isRequired, 41 | }); 42 | 43 | /* NavigationSceneRendererProps */ 44 | const SceneRendererProps = { 45 | layout: layout.isRequired, 46 | navigationState: navigationState.isRequired, 47 | position: animatedValue.isRequired, 48 | progress: animatedValue.isRequired, 49 | scene: scene.isRequired, 50 | scenes: PropTypes.arrayOf(scene).isRequired, 51 | }; 52 | 53 | const SceneRenderer = PropTypes.shape(SceneRendererProps); 54 | 55 | /* NavigationPanPanHandlers */ 56 | const panHandlers = PropTypes.shape({ 57 | onMoveShouldSetResponder: PropTypes.func.isRequired, 58 | onMoveShouldSetResponderCapture: PropTypes.func.isRequired, 59 | onResponderEnd: PropTypes.func.isRequired, 60 | onResponderGrant: PropTypes.func.isRequired, 61 | onResponderMove: PropTypes.func.isRequired, 62 | onResponderReject: PropTypes.func.isRequired, 63 | onResponderRelease: PropTypes.func.isRequired, 64 | onResponderStart: PropTypes.func.isRequired, 65 | onResponderTerminate: PropTypes.func.isRequired, 66 | onResponderTerminationRequest: PropTypes.func.isRequired, 67 | onStartShouldSetResponder: PropTypes.func.isRequired, 68 | onStartShouldSetResponderCapture: PropTypes.func.isRequired, 69 | }); 70 | 71 | /** 72 | * Helper function that extracts the props needed for scene renderer. 73 | */ 74 | function extractSceneRendererProps(props) { 75 | return { 76 | layout: props.layout, 77 | navigationState: props.navigationState, 78 | position: props.position, 79 | progress: props.progress, 80 | scene: props.scene, 81 | scenes: props.scenes, 82 | }; 83 | } 84 | 85 | module.exports = { 86 | // helpers 87 | extractSceneRendererProps, 88 | 89 | // Bundled propTypes. 90 | SceneRendererProps, 91 | 92 | // propTypes 93 | SceneRenderer, 94 | action, 95 | navigationState, 96 | navigationRoute, 97 | panHandlers, 98 | }; 99 | -------------------------------------------------------------------------------- /src/Libraries/NavigationExperimental/NavigationStateUtils.js: -------------------------------------------------------------------------------- 1 | function get(state, key) { 2 | return state.routes.find(route => route.key === key) || null; 3 | } 4 | 5 | function indexOf(state, key) { 6 | return state.routes.map(route => route.key).indexOf(key); 7 | } 8 | 9 | function has(state, key) { 10 | return !!state.routes.some(route => route.key === key); 11 | } 12 | 13 | function push(state, route) { 14 | if (indexOf(state, route.key) !== -1) { 15 | throw new Error('should not push route with duplicated key ' + route.key); 16 | } 17 | 18 | const routes = [ 19 | ...state.routes, 20 | route, 21 | ]; 22 | 23 | return { 24 | ...state, 25 | index: routes.length - 1, 26 | routes, 27 | }; 28 | } 29 | 30 | function pop(state) { 31 | if (state.index <= 0) { 32 | return state; 33 | } 34 | const routes = state.routes.slice(0, -1); 35 | return { 36 | ...state, 37 | index: routes.length - 1, 38 | routes, 39 | }; 40 | } 41 | 42 | function jumpToIndex(state, index: number) { 43 | if (index === state.index) { 44 | return state; 45 | } 46 | 47 | if (!state.routes[index]) { 48 | throw new Error('invalid index ' + index + ' to jump to'); 49 | } 50 | 51 | return { 52 | ...state, 53 | index, 54 | }; 55 | } 56 | 57 | 58 | function jumpTo(state, key) { 59 | const index = indexOf(state, key); 60 | return jumpToIndex(state, index); 61 | } 62 | 63 | function replaceAtIndex(state, index, route) { 64 | if (!state.routes[index]) { 65 | throw new Error('invalid index ' + index + ' for replacing route ' + route.key); 66 | } 67 | 68 | if (state.routes[index] === route) { 69 | return state; 70 | } 71 | 72 | const routes = state.routes.slice(); 73 | routes[index] = route; 74 | 75 | return { 76 | ...state, 77 | index, 78 | routes, 79 | }; 80 | } 81 | 82 | function replaceAt(state, key, route) { 83 | const index = indexOf(state, key); 84 | return replaceAtIndex(state, index, route); 85 | } 86 | 87 | function reset(state, routes, index = null) { 88 | if (!routes.length && Array.isArray(routes)) { 89 | throw new Error('invalid routes to replace'); 90 | } 91 | 92 | const nextIndex = index === undefined ? routes.length - 1 : index; 93 | 94 | if (state.routes.length === routes.length && state.index === nextIndex) { 95 | const compare = (route, ii) => routes[ii] === route; 96 | if (state.routes.every(compare)) { 97 | return state; 98 | } 99 | } 100 | 101 | if (!routes[nextIndex]) { 102 | throw new Error('invalid index ' + nextIndex + ' to reset'); 103 | } 104 | 105 | return { 106 | ...state, 107 | index: nextIndex, 108 | routes, 109 | }; 110 | } 111 | 112 | module.exports = { 113 | get, 114 | has, 115 | indexOf, 116 | jumpTo, 117 | jumpToIndex, 118 | pop, 119 | push, 120 | replaceAt, 121 | replaceAtIndex, 122 | reset, 123 | }; 124 | -------------------------------------------------------------------------------- /src/Libraries/NavigationExperimental/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/facebook/react-native/blob/master/Libraries/NavigationExperimental/NavigationExperimental.js 3 | */ 4 | import createMockComponent from '../../components/createMockComponent'; 5 | import StateUtils from './NavigationStateUtils'; 6 | import Card from './NavigationCard'; 7 | import PropTypes from './NavigationPropTypes'; 8 | 9 | module.exports = { 10 | StateUtils, 11 | 12 | AnimatedView: createMockComponent('NavigationAnimatedView'), 13 | Transitioner: createMockComponent('NavigationTransitioner'), 14 | 15 | Card, 16 | CardStack: createMockComponent('NavigationCardStack'), 17 | Header: createMockComponent('NavigationHeader'), 18 | 19 | PropTypes, 20 | }; 21 | -------------------------------------------------------------------------------- /src/Libraries/Network/FormData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule FormData 10 | * @flow 11 | */ 12 | 13 | type FormDataValue = any; 14 | type FormDataNameValuePair = [string, FormDataValue]; 15 | 16 | type Headers = {[name: string]: string}; 17 | type FormDataPart = { 18 | string: string, 19 | headers: Headers, 20 | } | { 21 | uri: string, 22 | headers: Headers, 23 | name?: string, 24 | type?: string, 25 | }; 26 | 27 | /** 28 | * Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests 29 | * with mixed data (string, native files) to be submitted via XMLHttpRequest. 30 | * 31 | * Example: 32 | * 33 | * var photo = { 34 | * uri: uriFromCameraRoll, 35 | * type: 'image/jpeg', 36 | * name: 'photo.jpg', 37 | * }; 38 | * 39 | * var body = new FormData(); 40 | * body.append('authToken', 'secret'); 41 | * body.append('photo', photo); 42 | * body.append('title', 'A beautiful photo!'); 43 | * 44 | * xhr.open('POST', serverURL); 45 | * xhr.send(body); 46 | */ 47 | class FormData { 48 | _parts: Array; 49 | 50 | constructor() { 51 | this._parts = []; 52 | } 53 | 54 | append(key: string, value: FormDataValue) { 55 | // The XMLHttpRequest spec doesn't specify if duplicate keys are allowed. 56 | // MDN says that any new values should be appended to existing values. 57 | // In any case, major browsers allow duplicate keys, so that's what we'll do 58 | // too. They'll simply get appended as additional form data parts in the 59 | // request body, leaving the server to deal with them. 60 | this._parts.push([key, value]); 61 | } 62 | 63 | getParts(): Array { 64 | return this._parts.map(([name, value]) => { 65 | const contentDisposition = 'form-data; name="' + name + '"'; 66 | 67 | const headers: Headers = { 'content-disposition': contentDisposition }; 68 | 69 | // The body part is a "blob", which in React Native just means 70 | // an object with a `uri` attribute. Optionally, it can also 71 | // have a `name` and `type` attribute to specify filename and 72 | // content type (cf. web Blob interface.) 73 | if (typeof value === 'object') { 74 | if (typeof value.name === 'string') { 75 | headers['content-disposition'] += '; filename="' + value.name + '"'; 76 | } 77 | if (typeof value.type === 'string') { 78 | headers['content-type'] = value.type; 79 | } 80 | return { ...value, headers, fieldName: name }; 81 | } 82 | // Convert non-object values to strings as per FormData.append() spec 83 | return { string: String(value), headers, fieldName: name }; 84 | }); 85 | } 86 | } 87 | 88 | module.exports = FormData; 89 | -------------------------------------------------------------------------------- /src/Libraries/Network/Headers.js: -------------------------------------------------------------------------------- 1 | class Headers { 2 | _headers: Object; 3 | 4 | constructor() { 5 | this._headers = []; 6 | } 7 | 8 | append(name: string, value: string) { 9 | const normalName: string = name.toLowerCase(); 10 | this._headers.push({ name: normalName, value }); 11 | } 12 | 13 | delete(name: string) { 14 | const normalName: string = name.toLowerCase(); 15 | this._headers = this._headers.filter((pair) => pair.name !== normalName); 16 | } 17 | 18 | entries() { 19 | return this._headers.entries(); 20 | } 21 | 22 | get(name: string) { 23 | const normalName: string = name.toLowerCase(); 24 | const header = this._headers.find((pair) => pair.name === normalName); 25 | return header ? header.value : undefined; 26 | } 27 | 28 | getAll(name: string) { 29 | const normalName: string = name.toLowerCase(); 30 | const headers = this._headers.filter((pair) => pair.name === normalName); 31 | return headers.map((pair) => pair.value); 32 | } 33 | 34 | has(name: string) { 35 | const normalName: string = name.toLowerCase(); 36 | return this.get(normalName); 37 | } 38 | 39 | keys() { 40 | return this._headers.map((pair) => pair.name); 41 | } 42 | 43 | set(name: string, value: string) { 44 | const normalName: string = name.toLowerCase(); 45 | this.delete(normalName); 46 | this.append(normalName, value); 47 | } 48 | 49 | values() { 50 | return this._headers.map((pair) => pair.value); 51 | } 52 | } 53 | 54 | module.exports = Headers; 55 | -------------------------------------------------------------------------------- /src/Libraries/Network/Response.js: -------------------------------------------------------------------------------- 1 | class Response { 2 | _status: number; 3 | _headers: Headers; 4 | _body: string; 5 | 6 | constructor() { 7 | this._status = 200; 8 | this._headers = new Headers(); 9 | this._body = ''; 10 | } 11 | 12 | get status(): number { 13 | return this._status; 14 | } 15 | 16 | get headers(): Headers { 17 | return this._headers; 18 | } 19 | 20 | get body(): string { 21 | return this._body; 22 | } 23 | } 24 | 25 | module.exports = Response; 26 | -------------------------------------------------------------------------------- /src/Libraries/Network/XMLHttpRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule XMLHttpRequest 10 | * @flow 11 | */ 12 | 13 | const UNSENT = 0; 14 | const OPENED = 1; 15 | const HEADERS_RECEIVED = 2; 16 | const LOADING = 3; 17 | const DONE = 4; 18 | 19 | class XMLHttpRequest { 20 | static UNSENT: number = UNSENT; 21 | static OPENED: number = OPENED; 22 | static HEADERS_RECEIVED: number = HEADERS_RECEIVED; 23 | static LOADING: number = LOADING; 24 | static DONE: number = DONE; 25 | 26 | UNSENT: number = UNSENT; 27 | OPENED: number = OPENED; 28 | HEADERS_RECEIVED: number = HEADERS_RECEIVED; 29 | LOADING: number = LOADING; 30 | DONE: number = DONE; 31 | 32 | onload: ?Function; 33 | onloadstart: ?Function; 34 | onprogress: ?Function; 35 | ontimeout: ?Function; 36 | onerror: ?Function; 37 | onloadend: ?Function; 38 | onreadystatechange: ?Function; 39 | 40 | readyState: number = UNSENT; 41 | responseHeaders: ?Object; 42 | status: number = 0; 43 | timeout: number = 0; 44 | responseURL: ?string; 45 | 46 | upload: { 47 | addEventListener: ?Function; 48 | }; 49 | 50 | open(method: string, url: string, async: ?boolean): void { 51 | } 52 | 53 | send(data: any): void { 54 | } 55 | 56 | abort(): void { 57 | } 58 | } 59 | 60 | module.exports = XMLHttpRequest; 61 | -------------------------------------------------------------------------------- /src/NativeModules/ActionSheetManager.js: -------------------------------------------------------------------------------- 1 | 2 | const ActionSheetManager = { 3 | showActionSheetWithOptions(options, callback) { 4 | 5 | }, 6 | showShareActionSheetWithOptions(options, failure, success) { 7 | 8 | }, 9 | }; 10 | 11 | module.exports = ActionSheetManager; 12 | -------------------------------------------------------------------------------- /src/NativeModules/AlertManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/React/Modules/RCTAlertManager.m 3 | */ 4 | const AlertManager = { 5 | alertWithArgs(args, callback) { 6 | 7 | }, 8 | }; 9 | 10 | module.exports = AlertManager; 11 | -------------------------------------------------------------------------------- /src/NativeModules/AppState.js: -------------------------------------------------------------------------------- 1 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 2 | 3 | let _appState = 'active'; 4 | 5 | DeviceEventEmitter.on('appStateDidChange', data => { 6 | _appState = data._appState; 7 | }); 8 | 9 | const AppState = { 10 | getCurrentAppState(callback, error) { 11 | Promise.resolve({ _appState }).then(callback); 12 | }, 13 | 14 | __setAppState(appState) { 15 | DeviceEventEmitter.emit('appStateDidChange', { _appState: appState }); 16 | }, 17 | }; 18 | 19 | module.exports = AppState; 20 | -------------------------------------------------------------------------------- /src/NativeModules/CameraRollManager.js: -------------------------------------------------------------------------------- 1 | 2 | const CameraRollManager = { 3 | saveImageWithTag(imageTag) { 4 | return Promise.resolve(['/asset/url']); 5 | }, 6 | getPhotos(params) { 7 | return Promise.resolve([ 8 | // TODO(lmr): 9 | ]); 10 | }, 11 | }; 12 | 13 | module.exports = CameraRollManager; 14 | -------------------------------------------------------------------------------- /src/NativeModules/Clipboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/Clipboard/Clipboard.js 3 | */ 4 | let _content = null; 5 | 6 | const Clipboard = { 7 | getString() { 8 | return Promise.resolve(_content); 9 | }, 10 | 11 | setString(content) { 12 | _content = content; 13 | }, 14 | }; 15 | 16 | module.exports = Clipboard; 17 | -------------------------------------------------------------------------------- /src/NativeModules/DatePickerAndroid.js: -------------------------------------------------------------------------------- 1 | // TODO(lmr): figure out a good way to have separate responses like "dismissed" vs "set". 2 | const DatePickerAndroid = { 3 | open(options) { 4 | return Promise.resolve().then({ action: 'dismissedAction' }); 5 | }, 6 | }; 7 | 8 | module.exports = DatePickerAndroid; 9 | -------------------------------------------------------------------------------- /src/NativeModules/DeviceEventManager.js: -------------------------------------------------------------------------------- 1 | const DeviceEventManager = { 2 | invokeDefaultBackPressHandler() { 3 | 4 | }, 5 | }; 6 | 7 | module.exports = DeviceEventManager; 8 | -------------------------------------------------------------------------------- /src/NativeModules/ImagePickerIOS.js: -------------------------------------------------------------------------------- 1 | const _canRecordVideos = true; 2 | const _canUseCamera = true; 3 | 4 | const ImagePickerIOS = { 5 | canRecordVideos(callback) { 6 | Promise.resolve(_canRecordVideos).then(callback); 7 | }, 8 | canUseCamera(callback) { 9 | Promise.resolve(_canUseCamera).then(callback); 10 | }, 11 | openCameraDialog(config, success, cancel) { 12 | // TODO(lmr): 13 | }, 14 | openSelectDialog(config, success, cancel) { 15 | // TODO(lmr): 16 | }, 17 | }; 18 | 19 | module.exports = ImagePickerIOS; 20 | -------------------------------------------------------------------------------- /src/NativeModules/LinkingManager.js: -------------------------------------------------------------------------------- 1 | let _test = url => true; 2 | const LinkingManger = { 3 | openURL(url) { 4 | return Promise.resolve(true); 5 | }, 6 | canOpenURL(url) { 7 | return Promise.resolve(_test(url)); 8 | }, 9 | 10 | __setCanOpenURLTest(test) { 11 | _test = test; 12 | } 13 | }; 14 | 15 | module.exports = LinkingManger; 16 | -------------------------------------------------------------------------------- /src/NativeModules/ScrollViewManager.js: -------------------------------------------------------------------------------- 1 | 2 | const ScrollViewManager = { 3 | getContentSize(reactTag, callback) { 4 | Promise.resolve().then(() => callback({ 5 | width: 20, 6 | height: 20, 7 | })); 8 | }, 9 | calculateChildFrames(reactTag, callback) { 10 | Promise.resolve().then(() => callback({ 11 | // TODO(lmr): 12 | })); 13 | }, 14 | endRefreshing(reactTag) { 15 | 16 | }, 17 | scrollTo(reactTag, offset, animated) { 18 | 19 | }, 20 | zoomToRect(reactTag, rect, animated) { 21 | 22 | }, 23 | DecelerationRate: { 24 | normal: 0, 25 | fast: 1, 26 | }, 27 | }; 28 | 29 | module.exports = ScrollViewManager; 30 | -------------------------------------------------------------------------------- /src/NativeModules/SourceCode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/React/Modules/RCTSourceCode.m 3 | */ 4 | let _sourceCode = null; 5 | 6 | const SourceCode = { 7 | getScriptText() { 8 | return _sourceCode 9 | ? Promise.resolve(_sourceCode) 10 | : Promise.reject(new Error('Source code is not available')); 11 | }, 12 | __setScriptText(url, text) { 13 | _sourceCode = !!url && !!text 14 | ? { url, text } 15 | : null; 16 | }, 17 | }; 18 | 19 | module.exports = SourceCode; 20 | -------------------------------------------------------------------------------- /src/NativeModules/TestModule.js: -------------------------------------------------------------------------------- 1 | import NativeAppEventEmitter from '../plugins/NativeAppEventEmitter'; 2 | 3 | const TestModule = { 4 | verifySnapshot(callback) { 5 | Promise.resolve().then(() => callback(true)); 6 | }, 7 | sendAppEvent(name, body) { 8 | NativeAppEventEmitter.emit(name, body); 9 | }, 10 | shouldResolve() { 11 | return Promise.resolve(1); 12 | }, 13 | shouldReject() { 14 | return Promise.reject(null); 15 | }, 16 | markTestCompleted() { 17 | 18 | }, 19 | markTestPassed(success) { 20 | 21 | }, 22 | }; 23 | 24 | module.exports = TestModule; 25 | -------------------------------------------------------------------------------- /src/NativeModules/TimePickerAndroid.js: -------------------------------------------------------------------------------- 1 | // TODO(lmr): figure out a good way to toggle between timeSetAction and dismissedAction 2 | let _resolver = () => ({ action: 'timeSetAction', hour: 2, minute: 30 }); 3 | const TimePickerAndroid = { 4 | open(options) { 5 | const result = _resolver(options) || { action: 'dismissedAction' }; 6 | return Promise.resolve(result); 7 | }, 8 | 9 | __setResolverFunction(resolver) { 10 | _resolver = resolver; 11 | } 12 | }; 13 | 14 | module.exports = TimePickerAndroid; 15 | -------------------------------------------------------------------------------- /src/NativeModules/Timing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/React/Modules/RCTTiming.m 3 | */ 4 | const Timing = { 5 | createTimer(callbackId, duration, jsSchedulingTime, repeats) { 6 | 7 | }, 8 | deleteTimer(timerId) { 9 | 10 | }, 11 | }; 12 | 13 | module.exports = Timing; 14 | -------------------------------------------------------------------------------- /src/NativeModules/UIManager.js: -------------------------------------------------------------------------------- 1 | 2 | const UIManager = { 3 | removeSubviewsFromContainerWithID(containerId) { 4 | 5 | }, 6 | removeRootView(rootReactTag) { 7 | 8 | }, 9 | replaceExistingNonRootView(reactTag, newReactTag) { 10 | 11 | }, 12 | setChildren(containerTag, reactTags) { 13 | 14 | }, 15 | manageChildren( 16 | containerReactTag, 17 | moveFromIndices, 18 | moveToIndices, 19 | addChildReactTags, 20 | addAtIndices, 21 | removeAtIndices 22 | ) { 23 | 24 | }, 25 | createView(reactTag, viewName, rootTag, props) { 26 | 27 | }, 28 | updateView(reactTag, viewName, props) { 29 | 30 | }, 31 | focus(reactTag) { 32 | 33 | }, 34 | blur(reactTag) { 35 | 36 | }, 37 | findSubviewIn(reactTag, atPoint, callback) { 38 | 39 | }, 40 | dispatchViewManagerCommand(reactTag, commandID, commandArgs) { 41 | 42 | }, 43 | measure(reactTag, callback) { 44 | 45 | }, 46 | measureLayout(reactTag, relativeTo, errorCallback, callback) { 47 | 48 | }, 49 | measureLayoutRelativeToParent(reactTag, errorCallback, callback) { 50 | 51 | }, 52 | measureViewsInRect(rect, parentView, errorCallback, callback) { 53 | 54 | }, 55 | setJSResponder(reactTag, blockNativeResponder) { 56 | 57 | }, 58 | clearJSResponder() { 59 | 60 | }, 61 | configureNextLayoutAnimation(callback, errorCallback) { 62 | 63 | }, 64 | AndroidDrawerLayout: { 65 | Constants: { 66 | DrawerPosition: { Left: 8388611, Right: 8388613 }, 67 | }, 68 | }, 69 | }; 70 | 71 | module.exports = UIManager; 72 | -------------------------------------------------------------------------------- /src/NativeModules/Vibration.js: -------------------------------------------------------------------------------- 1 | const Vibration = { 2 | vibrate() { 3 | 4 | }, 5 | }; 6 | 7 | module.exports = Vibration; 8 | -------------------------------------------------------------------------------- /src/NativeModules/WebViewManager.js: -------------------------------------------------------------------------------- 1 | 2 | const WebViewManager = { 3 | goBack(reactTag) { 4 | 5 | }, 6 | goForward(reactTag) { 7 | 8 | }, 9 | reload(reactTag) { 10 | 11 | }, 12 | startLoadWithResult(result, lockIdentifier) { 13 | 14 | }, 15 | JSNavigationScheme: 'react-js-navigation', 16 | NavigationType: { 17 | LinkClicked: 0, 18 | FormSubmitted: 1, 19 | BackForward: 2, 20 | Reload: 3, 21 | FormResubmitted: 4, 22 | Other: 5, 23 | } 24 | }; 25 | 26 | module.exports = WebViewManager; 27 | -------------------------------------------------------------------------------- /src/NativeModules/index.js: -------------------------------------------------------------------------------- 1 | 2 | const NativeModules = { 3 | Timing: require('./Timing'), 4 | UIManager: require('./UIManager'), 5 | AsyncLocalStorage: require('../api/AsyncStorage'), 6 | SourceCode: require('./SourceCode'), 7 | AlertManager: require('./AlertManager'), 8 | Clipboard: require('./Clipboard'), 9 | CameraRollManager: require('./CameraRollManager'), 10 | TestModule: require('./TestModule'), 11 | WebViewManager: require('./WebViewManager'), 12 | ScrollViewManager: require('./ScrollViewManager'), 13 | ActionSheetManager: require('./ActionSheetManager'), 14 | AppState: require('./AppState'), 15 | ImagePickerIOS: require('./ImagePickerIOS'), 16 | DeviceEventManager: require('./DeviceEventManager'), 17 | DatePickerAndroid: require('./DatePickerAndroid'), 18 | LinkingManager: require('./LinkingManager'), 19 | TimePickerAndroid: require('./TimePickerAndroid'), 20 | Vibration: require('./Vibration'), 21 | }; 22 | 23 | module.exports = NativeModules; 24 | -------------------------------------------------------------------------------- /src/api/ActionSheetIOS.js: -------------------------------------------------------------------------------- 1 | import ActionSheetManager from '../NativeModules/ActionSheetManager'; 2 | import invariant from 'invariant'; 3 | import processColor from '../plugins/processColor'; 4 | 5 | const ActionSheetIOS = { 6 | showActionSheetWithOptions(options, callback) { 7 | invariant( 8 | typeof options === 'object' && options !== null, 9 | 'Options must a valid object' 10 | ); 11 | invariant( 12 | typeof callback === 'function', 13 | 'Must provide a valid callback' 14 | ); 15 | ActionSheetManager.showActionSheetWithOptions( 16 | { ...options, tintColor: processColor(options.tintColor) }, 17 | callback 18 | ); 19 | }, 20 | 21 | showShareActionSheetWithOptions( 22 | options, 23 | failureCallback, 24 | successCallback 25 | ) { 26 | invariant( 27 | typeof options === 'object' && options !== null, 28 | 'Options must a valid object' 29 | ); 30 | invariant( 31 | typeof failureCallback === 'function', 32 | 'Must provide a valid failureCallback' 33 | ); 34 | invariant( 35 | typeof successCallback === 'function', 36 | 'Must provide a valid successCallback' 37 | ); 38 | ActionSheetManager.showShareActionSheetWithOptions( 39 | { ...options, tintColor: processColor(options.tintColor) }, 40 | failureCallback, 41 | successCallback 42 | ); 43 | } 44 | }; 45 | 46 | module.exports = ActionSheetIOS; 47 | -------------------------------------------------------------------------------- /src/api/Alert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Alert.js 3 | */ 4 | const Alert = { 5 | alert(title, message, buttons, type) { 6 | 7 | }, 8 | }; 9 | 10 | module.exports = Alert; 11 | -------------------------------------------------------------------------------- /src/api/AlertIOS.js: -------------------------------------------------------------------------------- 1 | import AlertManager from '../NativeModules/AlertManager'; 2 | /** 3 | * The AlertsIOS utility provides two functions: `alert` and `prompt`. All 4 | * functionality available through `AlertIOS.alert` is also available in the 5 | * cross-platform `Alert.alert`, which we recommend you use if you don't need 6 | * iOS-specific functionality. 7 | * 8 | * `AlertIOS.prompt` allows you to prompt the user for input inside of an 9 | * alert popup. 10 | * 11 | */ 12 | class AlertIOS { 13 | /** 14 | * Creates a popup to alert the user. See 15 | * [Alert](/react-native/docs/alert.html). 16 | * 17 | * - title: string -- The dialog's title. 18 | * - message: string -- An optional message that appears above the text input. 19 | * - callbackOrButtons -- This optional argument should be either a 20 | * single-argument function or an array of buttons. If passed a function, 21 | * it will be called when the user taps 'OK'. 22 | * 23 | * If passed an array of button configurations, each button should include 24 | * a `text` key, as well as optional `onPress` and `style` keys. 25 | * `style` should be one of 'default', 'cancel' or 'destructive'. 26 | * - type -- *deprecated, do not use* 27 | * 28 | * Example: 29 | * 30 | * ``` 31 | * AlertIOS.alert( 32 | * 'Sync Complete', 33 | * 'All your data are belong to us.' 34 | * ); 35 | * ``` 36 | */ 37 | static alert(title, message, callbackOrButtons, type) { 38 | if (typeof type !== 'undefined') { 39 | console.warn( 40 | 'AlertIOS.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.' 41 | ); 42 | this.prompt(title, message, callbackOrButtons, type); 43 | return; 44 | } 45 | this.prompt(title, message, callbackOrButtons, 'default'); 46 | } 47 | 48 | /** 49 | * Prompt the user to enter some text. 50 | * 51 | * - title: string -- The dialog's title. 52 | * - message: string -- An optional message that appears above the text input. 53 | * - callbackOrButtons -- This optional argument should be either a 54 | * single-argument function or an array of buttons. If passed a function, 55 | * it will be called with the prompt's value when the user taps 'OK'. 56 | * 57 | * If passed an array of button configurations, each button should include 58 | * a `text` key, as well as optional `onPress` and `style` keys (see example). 59 | * `style` should be one of 'default', 'cancel' or 'destructive'. 60 | * - type: string -- This configures the text input. One of 'plain-text', 61 | * 'secure-text' or 'login-password'. 62 | * - defaultValue: string -- the default value for the text field. 63 | * 64 | * Example with custom buttons: 65 | * ``` 66 | * AlertIOS.prompt( 67 | * 'Enter password', 68 | * 'Enter your password to claim your $1.5B in lottery winnings', 69 | * [ 70 | * {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, 71 | * {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)}, 72 | * ], 73 | * 'secure-text' 74 | * ); 75 | * ``` 76 | * 77 | * Example with the default button and a custom callback: 78 | * ``` 79 | * AlertIOS.prompt( 80 | * 'Update username', 81 | * null, 82 | * text => console.log("Your username is "+text), 83 | * null, 84 | * 'default' 85 | * ) 86 | * ``` 87 | */ 88 | static prompt(title, message, callbackOrButtons, type, defaultValue) { 89 | if (typeof type === 'function') { 90 | const callback = type; 91 | AlertManager.alertWithArgs({ 92 | title: title || undefined, 93 | type: 'plain-text', 94 | message, 95 | }, (id, value) => { 96 | callback(value); 97 | }); 98 | return; 99 | } 100 | 101 | let callbacks = []; 102 | const buttons = []; 103 | let cancelButtonKey; 104 | let destructiveButtonKey; 105 | if (typeof callbackOrButtons === 'function') { 106 | callbacks = [callbackOrButtons]; 107 | } else if (callbackOrButtons instanceof Array) { 108 | callbackOrButtons.forEach((btn, index) => { 109 | callbacks[index] = btn.onPress; 110 | if (btn.style === 'cancel') { 111 | cancelButtonKey = String(index); 112 | } else if (btn.style === 'destructive') { 113 | destructiveButtonKey = String(index); 114 | } 115 | if (btn.text || index < (callbackOrButtons || []).length - 1) { 116 | const btnDef = {}; 117 | btnDef[index] = btn.text || ''; 118 | buttons.push(btnDef); 119 | } 120 | }); 121 | } 122 | 123 | AlertManager.alertWithArgs( 124 | { 125 | title: title || undefined, 126 | message: message || undefined, 127 | buttons, 128 | type: type || undefined, 129 | defaultValue, 130 | cancelButtonKey, 131 | destructiveButtonKey, 132 | }, 133 | (id, value) => { 134 | const cb = callbacks[id]; 135 | if (cb) { 136 | cb(value); 137 | } 138 | } 139 | ); 140 | } 141 | } 142 | 143 | module.exports = AlertIOS; 144 | -------------------------------------------------------------------------------- /src/api/Animated/Easing.js: -------------------------------------------------------------------------------- 1 | import _bezier from 'cubic-bezier'; 2 | 3 | let _ease = () => {}; 4 | 5 | const EPSILON = (1000 / 60 / 500) / 4; 6 | 7 | /** 8 | * This class implements common easing functions. The math is pretty obscure, 9 | * but this cool website has nice visual illustrations of what they represent: 10 | * http://xaedes.de/dev/transitions/ 11 | */ 12 | class Easing { 13 | static step0(n) { 14 | return n > 0 ? 1 : 0; 15 | } 16 | 17 | static step1(n) { 18 | return n >= 1 ? 1 : 0; 19 | } 20 | 21 | static linear(t) { 22 | return t; 23 | } 24 | 25 | static ease(t) { 26 | return _ease(t); 27 | } 28 | 29 | static quad(t) { 30 | return t * t; 31 | } 32 | 33 | static cubic(t) { 34 | return t * t * t; 35 | } 36 | 37 | static poly(n) { 38 | return (t) => Math.pow(t, n); 39 | } 40 | 41 | static sin(t) { 42 | return 1 - Math.cos(t * Math.PI / 2); 43 | } 44 | 45 | static circle(t) { 46 | return 1 - Math.sqrt(1 - t * t); 47 | } 48 | 49 | static exp(t) { 50 | return Math.pow(2, 10 * (t - 1)); 51 | } 52 | 53 | /** 54 | * A simple elastic interaction, similar to a spring. Default bounciness 55 | * is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot 56 | * at all, and bounciness of N > 1 will overshoot about N times. 57 | * 58 | * Wolfram Plots: 59 | * 60 | * http://tiny.cc/elastic_b_1 (default bounciness = 1) 61 | * http://tiny.cc/elastic_b_3 (bounciness = 3) 62 | */ 63 | static elastic(bounciness = 1) { 64 | const p = bounciness * Math.PI; 65 | return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p); 66 | } 67 | 68 | static back(s = 1.70158) { 69 | return (t) => t * t * ((s + 1) * t - s); 70 | } 71 | 72 | static bounce(argT) { 73 | let t = argT; 74 | if (t < 1 / 2.75) { 75 | return 7.5625 * t * t; 76 | } 77 | 78 | if (t < 2 / 2.75) { 79 | t -= 1.5 / 2.75; 80 | return 7.5625 * t * t + 0.75; 81 | } 82 | 83 | if (t < 2.5 / 2.75) { 84 | t -= 2.25 / 2.75; 85 | return 7.5625 * t * t + 0.9375; 86 | } 87 | 88 | t -= 2.625 / 2.75; 89 | return 7.5625 * t * t + 0.984375; 90 | } 91 | 92 | static bezier(x1, y1, x2, y2, epsilon = EPSILON) { 93 | return _bezier(x1, y1, x2, y2, epsilon); 94 | } 95 | 96 | static in(easing) { 97 | return easing; 98 | } 99 | 100 | /** 101 | * Runs an easing function backwards. 102 | */ 103 | static out(easing) { 104 | return (t) => 1 - easing(1 - t); 105 | } 106 | 107 | /** 108 | * Makes any easing function symmetrical. 109 | */ 110 | static inOut(easing) { 111 | return (t) => { 112 | if (t < 0.5) { 113 | return easing(t * 2) / 2; 114 | } 115 | return 1 - easing((1 - t) * 2) / 2; 116 | }; 117 | } 118 | } 119 | 120 | _ease = Easing.bezier(0.42, 0, 1, 1); 121 | 122 | module.exports = Easing; 123 | -------------------------------------------------------------------------------- /src/api/Animated/Interpolation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/Interpolation.js 3 | */ 4 | class Interpolation { 5 | static create(config) { 6 | // TODO(lmr): 7 | } 8 | } 9 | 10 | module.exports = Interpolation; 11 | -------------------------------------------------------------------------------- /src/api/Animated/SpringConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/SpringConfig.js 3 | */ 4 | 5 | function tensionFromOrigamiValue(oValue) { 6 | return (oValue - 30) * 3.62 + 194; 7 | } 8 | 9 | function frictionFromOrigamiValue(oValue) { 10 | return (oValue - 8) * 3 + 25; 11 | } 12 | 13 | function fromOrigamiTensionAndFriction(tension, friction) { 14 | return { 15 | tension: tensionFromOrigamiValue(tension), 16 | friction: frictionFromOrigamiValue(friction) 17 | }; 18 | } 19 | 20 | function fromBouncinessAndSpeed(bounciness, speed) { 21 | function normalize(value, startValue, endValue) { 22 | return (value - startValue) / (endValue - startValue); 23 | } 24 | 25 | function projectNormal(n, start, end) { 26 | return start + (n * (end - start)); 27 | } 28 | 29 | function linearInterpolation(t, start, end) { 30 | return t * end + (1 - t) * start; 31 | } 32 | 33 | function quadraticOutInterpolation(t, start, end) { 34 | return linearInterpolation(2 * t - t * t, start, end); 35 | } 36 | 37 | function b3Friction1(x) { 38 | return (0.0007 * Math.pow(x, 3)) - 39 | (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; 40 | } 41 | 42 | function b3Friction2(x) { 43 | return (0.000044 * Math.pow(x, 3)) - 44 | (0.006 * Math.pow(x, 2)) + 0.36 * x + 2; 45 | } 46 | 47 | function b3Friction3(x) { 48 | return (0.00000045 * Math.pow(x, 3)) - 49 | (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; 50 | } 51 | 52 | function b3Nobounce(tension) { 53 | if (tension <= 18) { 54 | return b3Friction1(tension); 55 | } else if (tension > 18 && tension <= 44) { 56 | return b3Friction2(tension); 57 | } 58 | return b3Friction3(tension); 59 | } 60 | 61 | let b = normalize(bounciness / 1.7, 0, 20); 62 | b = projectNormal(b, 0, 0.8); 63 | const s = normalize(speed / 1.7, 0, 20); 64 | const bouncyTension = projectNormal(s, 0.5, 200); 65 | const bouncyFriction = quadraticOutInterpolation( 66 | b, 67 | b3Nobounce(bouncyTension), 68 | 0.01 69 | ); 70 | 71 | return { 72 | tension: tensionFromOrigamiValue(bouncyTension), 73 | friction: frictionFromOrigamiValue(bouncyFriction) 74 | }; 75 | } 76 | 77 | module.exports = { 78 | fromOrigamiTensionAndFriction, 79 | fromBouncinessAndSpeed, 80 | }; 81 | -------------------------------------------------------------------------------- /src/api/Animated/createAnimatedComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function createAnimatedComponent(Component) { 4 | const refName = 'node'; 5 | 6 | class AnimatedComponent extends React.Component { 7 | render() { 8 | return ( 9 | 13 | ); 14 | } 15 | } 16 | 17 | return AnimatedComponent; 18 | } 19 | 20 | module.exports = createAnimatedComponent; 21 | -------------------------------------------------------------------------------- /src/api/Animated/index.js: -------------------------------------------------------------------------------- 1 | import View from '../../components/View'; 2 | import Text from '../../components/Text'; 3 | import Image from '../../components/Image'; 4 | import createAnimatedComponent from './createAnimatedComponent'; 5 | import AnimatedImplementation from './AnimatedImplementation'; 6 | 7 | module.exports = { 8 | ...AnimatedImplementation, 9 | createAnimatedComponent, 10 | View: createAnimatedComponent(View), 11 | Text: createAnimatedComponent(Text), 12 | Image: createAnimatedComponent(Image), 13 | }; 14 | -------------------------------------------------------------------------------- /src/api/AppRegistry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/AppRegistry/AppRegistry.js 3 | */ 4 | const runnables = {}; 5 | 6 | const AppRegistry = { 7 | registerConfig(configs) { 8 | 9 | }, 10 | 11 | registerComponent(appKey, getComponentFunc) { 12 | return appKey; 13 | }, 14 | 15 | registerRunnable(appKey, func) { 16 | runnables[appKey] = { run: func }; 17 | return appKey; 18 | }, 19 | 20 | getAppKeys() { 21 | return Object.keys(runnables); 22 | }, 23 | 24 | runApplication(appKey, appParameters) { 25 | 26 | }, 27 | 28 | unmountApplicationComponentAtRootTag(rootTag) { 29 | 30 | }, 31 | }; 32 | 33 | module.exports = AppRegistry; 34 | -------------------------------------------------------------------------------- /src/api/AppState.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/AppState/AppState.js 3 | */ 4 | import invariant from 'invariant'; 5 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 6 | 7 | const _eventHandlers = { 8 | change: new Map(), 9 | memoryWarning: new Map(), 10 | }; 11 | 12 | const AppState = { 13 | addEventListener(type, handler) { 14 | invariant( 15 | ['change', 'memoryWarning'].indexOf(type) !== -1, 16 | 'Trying to subscribe to unknown event: "%s"', type 17 | ); 18 | if (type === 'change') { 19 | _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( 20 | 'appStateDidChange', 21 | (appStateData) => handler(appStateData.appState) 22 | )); 23 | } else if (type === 'memoryWarning') { 24 | _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( 25 | 'memoryWarning', 26 | handler 27 | )); 28 | } 29 | }, 30 | 31 | removeEventListener(type, handler) { 32 | invariant( 33 | ['change', 'memoryWarning'].indexOf(type) !== -1, 34 | 'Trying to remove listener for unknown event: "%s"', type 35 | ); 36 | if (!_eventHandlers[type].has(handler)) { 37 | return; 38 | } 39 | _eventHandlers[type].get(handler).remove(); 40 | _eventHandlers[type].delete(handler); 41 | }, 42 | 43 | currentState: 'active', 44 | 45 | __setAppState(appState) { 46 | DeviceEventEmitter.emit('appStateDidChange', { appState }); 47 | }, 48 | }; 49 | 50 | DeviceEventEmitter.addListener( 51 | 'appStateDidChange', 52 | (appStateData) => { AppState.currentState = appStateData.appState; } 53 | ); 54 | 55 | module.exports = AppState; 56 | -------------------------------------------------------------------------------- /src/api/AppStateIOS.js: -------------------------------------------------------------------------------- 1 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 2 | import AppState from '../NativeModules/AppState'; 3 | import invariant from 'invariant'; 4 | 5 | const logError = (error) => console.error(error); 6 | 7 | const _eventHandlers = { 8 | change: new Map(), 9 | memoryWarning: new Map(), 10 | }; 11 | 12 | /** 13 | * `AppStateIOS` can tell you if the app is in the foreground or background, 14 | * and notify you when the state changes. 15 | * 16 | * AppStateIOS is frequently used to determine the intent and proper behavior when 17 | * handling push notifications. 18 | * 19 | * ### iOS App States 20 | * 21 | * - `active` - The app is running in the foreground 22 | * - `background` - The app is running in the background. The user is either 23 | * in another app or on the home screen 24 | * - `inactive` - This is a transition state that currently never happens for 25 | * typical React Native apps. 26 | * 27 | * For more information, see 28 | * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html) 29 | * 30 | * ### Basic Usage 31 | * 32 | * To see the current state, you can check `AppStateIOS.currentState`, which 33 | * will be kept up-to-date. However, `currentState` will be null at launch 34 | * while `AppStateIOS` retrieves it over the bridge. 35 | * 36 | * ``` 37 | * getInitialState: function() { 38 | * return { 39 | * currentAppState: AppStateIOS.currentState, 40 | * }; 41 | * }, 42 | * componentDidMount: function() { 43 | * AppStateIOS.addEventListener('change', this._handleAppStateChange); 44 | * }, 45 | * componentWillUnmount: function() { 46 | * AppStateIOS.removeEventListener('change', this._handleAppStateChange); 47 | * }, 48 | * _handleAppStateChange: function(currentAppState) { 49 | * this.setState({ currentAppState, }); 50 | * }, 51 | * render: function() { 52 | * return ( 53 | * Current state is: {this.state.currentAppState} 54 | * ); 55 | * }, 56 | * ``` 57 | * 58 | * This example will only ever appear to say "Current state is: active" because 59 | * the app is only visible to the user when in the `active` state, and the null 60 | * state will happen only momentarily. 61 | */ 62 | 63 | const AppStateIOS = { 64 | 65 | /** 66 | * Add a handler to AppState changes by listening to the `change` event type 67 | * and providing the handler 68 | */ 69 | addEventListener(type, handler) { 70 | invariant( 71 | ['change', 'memoryWarning'].indexOf(type) !== -1, 72 | 'Trying to subscribe to unknown event: "%s"', type 73 | ); 74 | if (type === 'change') { 75 | _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( 76 | 'appStateDidChange', 77 | (appStateData) => { 78 | handler(appStateData.app_state); 79 | } 80 | )); 81 | } else if (type === 'memoryWarning') { 82 | _eventHandlers[type].set(handler, DeviceEventEmitter.addListener( 83 | 'memoryWarning', 84 | handler 85 | )); 86 | } 87 | }, 88 | 89 | /** 90 | * Remove a handler by passing the `change` event type and the handler 91 | */ 92 | removeEventListener(type, handler) { 93 | invariant( 94 | ['change', 'memoryWarning'].indexOf(type) !== -1, 95 | 'Trying to remove listener for unknown event: "%s"', type 96 | ); 97 | if (!_eventHandlers[type].has(handler)) { 98 | return; 99 | } 100 | _eventHandlers[type].get(handler).remove(); 101 | _eventHandlers[type].delete(handler); 102 | }, 103 | 104 | // TODO: getCurrentAppState callback seems to be called at a really late stage 105 | // after app launch. Trying to get currentState when mounting App component 106 | // will likely to have the initial value here. 107 | // Initialize to 'active' instead of null. 108 | currentState: 'active', 109 | 110 | }; 111 | 112 | DeviceEventEmitter.addListener( 113 | 'appStateDidChange', 114 | (appStateData) => { 115 | AppStateIOS.currentState = appStateData.app_state; 116 | } 117 | ); 118 | 119 | AppState.getCurrentAppState( 120 | (appStateData) => { 121 | AppStateIOS.currentState = appStateData.app_state; 122 | }, 123 | logError 124 | ); 125 | 126 | module.exports = AppStateIOS; 127 | -------------------------------------------------------------------------------- /src/api/AsyncStorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Storage/AsyncStorage.js 3 | */ 4 | 5 | function wrap(value, callback) { 6 | return Promise.resolve(value).then( 7 | obj => { 8 | if (callback) { 9 | callback(null, obj); 10 | } 11 | return obj; 12 | }, 13 | err => { 14 | if (callback) { 15 | callback(err); 16 | } 17 | throw err; 18 | } 19 | ); 20 | } 21 | 22 | let db = {}; 23 | 24 | const AsyncStorage = { 25 | getItem(key, callback) { 26 | return wrap(db[key] || null, callback); 27 | }, 28 | 29 | setItem(key, value, callback) { 30 | db[key] = value; 31 | return wrap(null, callback); 32 | }, 33 | 34 | removeItem(key, callback) { 35 | delete db[key]; 36 | return wrap(null, callback); 37 | }, 38 | 39 | mergeItem(key, value, callback) { 40 | db[key] = Object.assign({}, db[key] || {}, value); 41 | return wrap(null, callback); 42 | }, 43 | 44 | clear(callback) { 45 | db = {}; 46 | return wrap(null, callback); 47 | }, 48 | 49 | getAllKeys(callback) { 50 | return wrap(Object.keys(db), callback); 51 | }, 52 | 53 | flushGetRequests() { 54 | 55 | }, 56 | 57 | multiGet(keys, callback) { 58 | return wrap(keys.map(k => [k, db[k] || null]), callback); 59 | }, 60 | 61 | multiSet(keyValuePairs, callback) { 62 | keyValuePairs.forEach(([key, value]) => { 63 | db[key] = value; 64 | }); 65 | return wrap(null, callback); 66 | }, 67 | 68 | multiRemove(keys, callback) { 69 | keys.forEach(key => delete db[key]); 70 | return wrap(null, callback); 71 | }, 72 | 73 | multiMerge(keyValuePairs, callback) { 74 | keyValuePairs.forEach(([key, value]) => { 75 | db[key] = Object.asign({}, db[key] || {}, value); 76 | }); 77 | return wrap(null, callback); 78 | }, 79 | }; 80 | 81 | module.exports = AsyncStorage; 82 | -------------------------------------------------------------------------------- /src/api/BackAndroid.js: -------------------------------------------------------------------------------- 1 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 2 | import DeviceEventManager from '../NativeModules/DeviceEventManager'; 3 | 4 | const DEVICE_BACK_EVENT = 'hardwareBackPress'; 5 | 6 | const _backPressSubscriptions = new Set(); 7 | 8 | /** 9 | * Detect hardware back button presses, and programmatically invoke the default back button 10 | * functionality to exit the app if there are no listeners or if none of the listeners return true. 11 | * 12 | * Example: 13 | * 14 | * ```js 15 | * BackAndroid.addEventListener('hardwareBackPress', function() { 16 | * if (!this.onMainScreen()) { 17 | * this.goBack(); 18 | * return true; 19 | * } 20 | * return false; 21 | * }); 22 | * ``` 23 | */ 24 | const BackAndroid = { 25 | 26 | exitApp() { 27 | DeviceEventManager.invokeDefaultBackPressHandler(); 28 | }, 29 | 30 | addEventListener(eventName, handler) { 31 | _backPressSubscriptions.add(handler); 32 | return { 33 | remove: () => BackAndroid.removeEventListener(eventName, handler), 34 | }; 35 | }, 36 | 37 | removeEventListener(eventName, handler) { 38 | _backPressSubscriptions.delete(handler); 39 | }, 40 | 41 | }; 42 | 43 | DeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function () { 44 | let invokeDefault = true; 45 | _backPressSubscriptions.forEach((subscription) => { 46 | if (subscription()) { 47 | invokeDefault = false; 48 | } 49 | }); 50 | if (invokeDefault) { 51 | BackAndroid.exitApp(); 52 | } 53 | }); 54 | 55 | module.exports = BackAndroid; 56 | -------------------------------------------------------------------------------- /src/api/CameraRoll.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import React from 'react'; 3 | import CameraRollManager from '../NativeModules/CameraRollManager'; 4 | 5 | const { PropTypes } = React; 6 | 7 | const GROUP_TYPES_OPTIONS = [ 8 | 'Album', 9 | 'All', 10 | 'Event', 11 | 'Faces', 12 | 'Library', 13 | 'PhotoStream', 14 | 'SavedPhotos', // default 15 | ]; 16 | 17 | const ASSET_TYPE_OPTIONS = [ 18 | 'All', 19 | 'Videos', 20 | 'Photos', // default 21 | ]; 22 | 23 | /** 24 | * Shape of the param arg for the `getPhotos` function. 25 | */ 26 | const getPhotosParamChecker = PropTypes.shape({ 27 | /** 28 | * The number of photos wanted in reverse order of the photo application 29 | * (i.e. most recent first for SavedPhotos). 30 | */ 31 | first: PropTypes.number.isRequired, 32 | 33 | /** 34 | * A cursor that matches `page_info { end_cursor }` returned from a previous 35 | * call to `getPhotos` 36 | */ 37 | after: PropTypes.string, 38 | 39 | /** 40 | * Specifies which group types to filter the results to. 41 | */ 42 | groupTypes: PropTypes.oneOf(GROUP_TYPES_OPTIONS), 43 | 44 | /** 45 | * Specifies filter on group names, like 'Recent Photos' or custom album 46 | * titles. 47 | */ 48 | groupName: PropTypes.string, 49 | 50 | /** 51 | * Specifies filter on asset type 52 | */ 53 | assetType: PropTypes.oneOf(ASSET_TYPE_OPTIONS), 54 | 55 | /** 56 | * Filter by mimetype (e.g. image/jpeg). 57 | */ 58 | mimeTypes: PropTypes.arrayOf(PropTypes.string), 59 | }); 60 | 61 | class CameraRoll { 62 | 63 | /** 64 | * Saves the image to the camera roll / gallery. 65 | * 66 | * On Android, the tag is a local URI, such as `"file:///sdcard/img.png"`. 67 | * 68 | * On iOS, the tag can be one of the following: 69 | * 70 | * - local URI 71 | * - assets-library tag 72 | * - a tag not matching any of the above, which means the image data will 73 | * be stored in memory (and consume memory as long as the process is alive) 74 | * 75 | * Returns a Promise which when resolved will be passed the new URI. 76 | */ 77 | static saveImageWithTag(tag) { 78 | invariant( 79 | typeof tag === 'string', 80 | 'CameraRoll.saveImageWithTag tag must be a valid string.' 81 | ); 82 | // TODO(lmr): 83 | return CameraRollManager.saveImageWithTag(tag); 84 | } 85 | 86 | /** 87 | * Returns a Promise with photo identifier objects from the local camera 88 | * roll of the device matching shape defined by `getPhotosReturnChecker`. 89 | * 90 | * @param {object} params See `getPhotosParamChecker`. 91 | * 92 | * Returns a Promise which when resolved will be of shape `getPhotosReturnChecker`. 93 | */ 94 | static getPhotos(params) { 95 | if (process.env.NODE_ENV === 'development') { 96 | getPhotosParamChecker({ params }, 'params', 'CameraRoll.getPhotos'); 97 | } 98 | // TODO(lmr): 99 | // TODO: Add the __DEV__ check back in to verify the Promise result 100 | return CameraRollManager.getPhotos(params); 101 | } 102 | } 103 | 104 | CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS; 105 | CameraRoll.AssetTypeOptions = ASSET_TYPE_OPTIONS; 106 | 107 | module.exports = CameraRoll; 108 | -------------------------------------------------------------------------------- /src/api/DatePickerAndroid.js: -------------------------------------------------------------------------------- 1 | import DatePickerModule from '../NativeModules/DatePickerAndroid'; 2 | 3 | /** 4 | * Convert a Date to a timestamp. 5 | */ 6 | function _toMillis(dateVal) { 7 | // Is it a Date object? 8 | if (typeof dateVal === 'object' && typeof dateVal.getMonth === 'function') { 9 | return dateVal.getTime(); 10 | } 11 | return null; 12 | } 13 | 14 | /** 15 | * Opens the standard Android date picker dialog. 16 | * 17 | * ### Example 18 | * 19 | * ``` 20 | * try { 21 | * const {action, year, month, day} = await DatePickerAndroid.open({ 22 | * // Use `new Date()` for current date. 23 | * // May 25 2020. Month 0 is January. 24 | * date: new Date(2020, 4, 25) 25 | * }); 26 | * if (action !== DatePickerAndroid.dismissedAction) { 27 | * // Selected year, month (0-11), day 28 | * } 29 | * } catch ({code, message}) { 30 | * console.warn('Cannot open date picker', message); 31 | * } 32 | * ``` 33 | */ 34 | class DatePickerAndroid { 35 | /** 36 | * Opens the standard Android date picker dialog. 37 | * 38 | * The available keys for the `options` object are: 39 | * * `date` (`Date` object or timestamp in milliseconds) - date to show by default 40 | * * `minDate` (`Date` or timestamp in milliseconds) - minimum date that can be selected 41 | * * `maxDate` (`Date` object or timestamp in milliseconds) - minimum date that can be selected 42 | * 43 | * Returns a Promise which will be invoked an object containing `action`, `year`, `month` (0-11), 44 | * `day` if the user picked a date. If the user dismissed the dialog, the Promise will 45 | * still be resolved with action being `DatePickerAndroid.dismissedAction` and all the other keys 46 | * being undefined. **Always** check whether the `action` before reading the values. 47 | * 48 | * Note the native date picker dialog has some UI glitches on Android 4 and lower 49 | * when using the `minDate` and `maxDate` options. 50 | */ 51 | static open(options) { 52 | const optionsMs = options; 53 | if (optionsMs) { 54 | optionsMs.date = _toMillis(options.date); 55 | optionsMs.minDate = _toMillis(options.minDate); 56 | optionsMs.maxDate = _toMillis(options.maxDate); 57 | } 58 | return DatePickerModule.open(optionsMs); 59 | } 60 | 61 | /** 62 | * A date has been selected. 63 | */ 64 | static get dateSetAction() { return 'dateSetAction'; } 65 | /** 66 | * The dialog has been dismissed. 67 | */ 68 | static get dismissedAction() { return 'dismissedAction'; } 69 | } 70 | 71 | module.exports = DatePickerAndroid; 72 | -------------------------------------------------------------------------------- /src/api/Dimensions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Dimensions.js 3 | */ 4 | const dimensions = { 5 | // TODO(lmr): find the other dimensions to put in here... 6 | window: { 7 | width: 320, 8 | height: 768, 9 | scale: 2, 10 | fontScale: 2, 11 | }, 12 | }; 13 | 14 | const Dimensions = { 15 | set(dims) { 16 | Object.assign(dimensions, dims); 17 | return true; 18 | }, 19 | get(dim) { 20 | return dimensions[dim]; 21 | }, 22 | }; 23 | 24 | module.exports = Dimensions; 25 | -------------------------------------------------------------------------------- /src/api/ImagePickerIOS.js: -------------------------------------------------------------------------------- 1 | 2 | import ImagePicker from '../NativeModules/ImagePickerIOS'; 3 | 4 | const ImagePickerIOS = { 5 | canRecordVideos(callback) { 6 | return ImagePicker.canRecordVideos(callback); 7 | }, 8 | canUseCamera(callback) { 9 | return ImagePicker.canUseCamera(callback); 10 | }, 11 | openCameraDialog(config, successCallback, cancelCallback) { 12 | const newConfig = { 13 | videoMode: false, 14 | ...config, 15 | }; 16 | return ImagePicker.openCameraDialog(newConfig, successCallback, cancelCallback); 17 | }, 18 | openSelectDialog(config, successCallback, cancelCallback) { 19 | const newConfig = { 20 | showImages: true, 21 | showVideos: false, 22 | ...config, 23 | }; 24 | return ImagePicker.openSelectDialog(newConfig, successCallback, cancelCallback); 25 | }, 26 | }; 27 | 28 | module.exports = ImagePickerIOS; 29 | -------------------------------------------------------------------------------- /src/api/IntentAndroid.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import Linking from './Linking'; 3 | 4 | class IntentAndroid { 5 | 6 | /** 7 | * Starts a corresponding external activity for the given URL. 8 | * 9 | * For example, if the URL is "https://www.facebook.com", the system browser will be opened, 10 | * or the "choose application" dialog will be shown. 11 | * 12 | * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, 13 | * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. 14 | * 15 | * NOTE: This method will fail if the system doesn't know how to open the specified URL. 16 | * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first. 17 | * 18 | * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! 19 | * 20 | * @deprecated 21 | */ 22 | static openURL(url) { 23 | console.warn( 24 | '"IntentAndroid" is deprecated. Use the promise based "Linking" instead.' 25 | ); 26 | Linking.openURL(url); 27 | } 28 | 29 | /** 30 | * Determine whether or not an installed app can handle a given URL. 31 | * 32 | * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, 33 | * or any other URL that can be opened with {@code Intent.ACTION_VIEW}. 34 | * 35 | * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! 36 | * 37 | * @param URL the URL to open 38 | * 39 | * @deprecated 40 | */ 41 | static canOpenURL(url, callback) { 42 | invariant( 43 | typeof callback === 'function', 44 | 'A valid callback function is required' 45 | ); 46 | Linking.canOpenURL(url).then(callback); 47 | } 48 | 49 | /** 50 | * If the app launch was triggered by an app link with {@code Intent.ACTION_VIEW}, 51 | * it will give the link url, otherwise it will give `null` 52 | * 53 | * Refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents 54 | * 55 | * @deprecated 56 | */ 57 | static getInitialURL(callback) { 58 | invariant( 59 | typeof callback === 'function', 60 | 'A valid callback function is required' 61 | ); 62 | Linking.getInitialURL().then(callback); 63 | } 64 | } 65 | 66 | module.exports = IntentAndroid; 67 | -------------------------------------------------------------------------------- /src/api/InteractionManager.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'keymirror'; 2 | import invariant from 'invariant'; 3 | 4 | const { EventEmitter } = require('events'); 5 | 6 | const _emitter = new EventEmitter(); 7 | 8 | let _inc = 0; 9 | let _deadline = -1; // eslint-disable-line no-unused-vars 10 | 11 | const InteractionManager = { 12 | Events: keyMirror({ 13 | interactionStart: true, 14 | interactionComplete: true, 15 | }), 16 | 17 | /** 18 | * Schedule a function to run after all interactions have completed. 19 | */ 20 | runAfterInteractions(task) { 21 | return new Promise(resolve => { 22 | // TODO(lmr): 23 | // _scheduleUpdate(); 24 | // if (task) { 25 | // _taskQueue.enqueue(task); 26 | // } 27 | // const name = task && task.name || '?'; 28 | // _taskQueue.enqueue({ run: resolve, name: 'resolve ' + name }); 29 | }); 30 | }, 31 | 32 | /** 33 | * Notify manager that an interaction has started. 34 | */ 35 | createInteractionHandle() { 36 | // TODO(lmr): 37 | // _scheduleUpdate(); 38 | const handle = ++_inc; 39 | // _addInteractionSet.add(handle); 40 | return handle; 41 | }, 42 | 43 | /** 44 | * Notify manager that an interaction has completed. 45 | */ 46 | clearInteractionHandle(handle) { 47 | invariant( 48 | !!handle, 49 | 'Must provide a handle to clear.' 50 | ); 51 | // TODO(lmr): 52 | // _scheduleUpdate(); 53 | // _addInteractionSet.delete(handle); 54 | // _deleteInteractionSet.add(handle); 55 | }, 56 | 57 | addListener: _emitter.addListener.bind(_emitter), 58 | 59 | /** 60 | * A positive number will use setTimeout to schedule any tasks after the 61 | * eventLoopRunningTime hits the deadline value, otherwise all tasks will be 62 | * executed in one setImmediate batch (default). 63 | */ 64 | setDeadline(deadline) { 65 | _deadline = deadline; 66 | }, 67 | }; 68 | 69 | module.exports = InteractionManager; 70 | -------------------------------------------------------------------------------- /src/api/Keyboard.js: -------------------------------------------------------------------------------- 1 | const Keyboard = { 2 | addListener(eventname, handler) { 3 | return { 4 | remove: () => {} 5 | }; 6 | }, 7 | removeListener: () => {}, 8 | removeAllListeners: () => {}, 9 | dismiss: () => {} 10 | }; 11 | 12 | module.exports = Keyboard; 13 | -------------------------------------------------------------------------------- /src/api/LayoutAnimation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UIManager from '../NativeModules/UIManager'; 3 | import keyMirror from 'keymirror'; 4 | 5 | const { PropTypes } = React; 6 | 7 | const TypesEnum = { 8 | spring: true, 9 | linear: true, 10 | easeInEaseOut: true, 11 | easeIn: true, 12 | easeOut: true, 13 | keyboard: true, 14 | }; 15 | 16 | const Types = keyMirror(TypesEnum); 17 | 18 | const PropertiesEnum = { 19 | opacity: true, 20 | scaleXY: true, 21 | }; 22 | 23 | const Properties = keyMirror(PropertiesEnum); 24 | 25 | const animChecker = PropTypes.shape({ 26 | duration: PropTypes.number, 27 | delay: PropTypes.number, 28 | springDamping: PropTypes.number, 29 | initialVelocity: PropTypes.number, 30 | type: PropTypes.oneOf( 31 | Object.keys(Types) 32 | ), 33 | property: PropTypes.oneOf( // Only applies to create/delete 34 | Object.keys(Properties) 35 | ), 36 | }); 37 | 38 | const configChecker = PropTypes.shape({ 39 | duration: PropTypes.number.isRequired, 40 | create: animChecker, 41 | update: animChecker, 42 | delete: animChecker, 43 | }); 44 | 45 | const nop = () => {}; 46 | 47 | function configureNext(config, onAnimationDidEnd) { 48 | configChecker({ config }, 'config', 'LayoutAnimation.configureNext'); 49 | UIManager.configureNextLayoutAnimation( 50 | config, 51 | onAnimationDidEnd || nop, 52 | nop 53 | ); 54 | } 55 | 56 | function create(duration, type, creationProp) { 57 | return { 58 | duration, 59 | create: { 60 | type, 61 | property: creationProp, 62 | }, 63 | update: { 64 | type, 65 | }, 66 | }; 67 | } 68 | 69 | const Presets = { 70 | easeInEaseOut: create( 71 | 300, Types.easeInEaseOut, Properties.opacity 72 | ), 73 | linear: create( 74 | 500, Types.linear, Properties.opacity 75 | ), 76 | spring: { 77 | duration: 700, 78 | create: { 79 | type: Types.linear, 80 | property: Properties.opacity, 81 | }, 82 | update: { 83 | type: Types.spring, 84 | springDamping: 0.4, 85 | }, 86 | }, 87 | }; 88 | 89 | const LayoutAnimation = { 90 | /** 91 | * Schedules an animation to happen on the next layout. 92 | * 93 | * @param config Specifies animation properties: 94 | * 95 | * - `duration` in milliseconds 96 | * - `create`, config for animating in new views (see `Anim` type) 97 | * - `update`, config for animating views that have been updated 98 | * (see `Anim` type) 99 | * 100 | * @param onAnimationDidEnd Called when the animation finished. 101 | * Only supported on iOS. 102 | * @param onError Called on error. Only supported on iOS. 103 | */ 104 | configureNext, 105 | /** 106 | * Helper for creating a config for `configureNext`. 107 | */ 108 | create, 109 | Types, 110 | Properties, 111 | configChecker, 112 | Presets, 113 | easeInEaseOut: configureNext.bind( 114 | null, Presets.easeInEaseOut 115 | ), 116 | linear: configureNext.bind( 117 | null, Presets.linear 118 | ), 119 | spring: configureNext.bind( 120 | null, Presets.spring 121 | ), 122 | }; 123 | 124 | module.exports = LayoutAnimation; 125 | -------------------------------------------------------------------------------- /src/api/Linking.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import Platform from '../plugins/Platform'; 3 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 4 | import LinkingManager from '../NativeModules/LinkingManager'; 5 | import IntentAndroid from './IntentAndroid'; 6 | import LinkingManagerIOS from './LinkingIOS'; 7 | 8 | const _notifHandlers = new Map(); 9 | 10 | const DEVICE_NOTIF_EVENT = 'openURL'; 11 | 12 | // TODO(lmr): 13 | class Linking { 14 | /** 15 | * Add a handler to Linking changes by listening to the `url` event type 16 | * and providing the handler 17 | * 18 | * @platform ios 19 | */ 20 | static addEventListener(type, handler) { 21 | if (Platform.OS === 'android') { 22 | console.warn( 23 | 'Linking.addEventListener is not supported on Android' 24 | ); 25 | } else { 26 | invariant( 27 | type === 'url', 28 | 'Linking only supports `url` events' 29 | ); 30 | const listener = DeviceEventEmitter.addListener( 31 | DEVICE_NOTIF_EVENT, 32 | handler 33 | ); 34 | _notifHandlers.set(handler, listener); 35 | } 36 | } 37 | 38 | /** 39 | * Remove a handler by passing the `url` event type and the handler 40 | * 41 | * @platform ios 42 | */ 43 | static removeEventListener(type, handler) { 44 | if (Platform.OS === 'android') { 45 | console.warn( 46 | 'Linking.removeEventListener is not supported on Android' 47 | ); 48 | } else { 49 | invariant( 50 | type === 'url', 51 | 'Linking only supports `url` events' 52 | ); 53 | const listener = _notifHandlers.get(handler); 54 | if (!listener) { 55 | return; 56 | } 57 | listener.removeListener( 58 | DEVICE_NOTIF_EVENT, 59 | handler 60 | ); 61 | _notifHandlers.delete(handler); 62 | } 63 | } 64 | 65 | /** 66 | * Try to open the given `url` with any of the installed apps. 67 | * 68 | * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact, 69 | * or any other URL that can be opened with the installed apps. 70 | * 71 | * NOTE: This method will fail if the system doesn't know how to open the specified URL. 72 | * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first. 73 | * 74 | * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! 75 | */ 76 | static openURL(url) { 77 | this._validateURL(url); 78 | return LinkingManager.openURL(url); 79 | } 80 | 81 | /** 82 | * Determine whether or not an installed app can handle a given URL. 83 | * 84 | * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly! 85 | * 86 | * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key 87 | * inside `Info.plist`. 88 | * 89 | * @param URL the URL to open 90 | */ 91 | static canOpenURL(url) { 92 | this._validateURL(url); 93 | return LinkingManager.canOpenURL(url); 94 | } 95 | 96 | /** 97 | * If the app launch was triggered by an app link with, 98 | * it will give the link url, otherwise it will give `null` 99 | * 100 | * NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents 101 | */ 102 | static getInitialURL() { 103 | if (Platform.OS === 'android') { 104 | return IntentAndroid.getInitialURL(); 105 | } 106 | return Promise.resolve(LinkingManagerIOS.initialURL); 107 | } 108 | 109 | static _validateURL(url) { 110 | invariant( 111 | typeof url === 'string', 112 | `Invalid URL: should be a string. Was: ${url}` 113 | ); 114 | invariant( 115 | url, 116 | 'Invalid URL: cannot be empty' 117 | ); 118 | } 119 | } 120 | 121 | module.exports = Linking; 122 | -------------------------------------------------------------------------------- /src/api/LinkingIOS.js: -------------------------------------------------------------------------------- 1 | import LinkingManager from '../NativeModules/LinkingManager'; 2 | import Linking from './Linking'; 3 | import invariant from 'invariant'; 4 | 5 | let _initialURL = LinkingManager && LinkingManager.initialURL; 6 | 7 | class LinkingIOS { 8 | /** 9 | * Add a handler to LinkingIOS changes by listening to the `url` event type 10 | * and providing the handler 11 | * 12 | * @deprecated 13 | */ 14 | static addEventListener(type, handler) { 15 | console.warn( 16 | '"LinkingIOS.addEventListener" is deprecated. Use "Linking.addEventListener" instead.' 17 | ); 18 | Linking.addEventListener(type, handler); 19 | } 20 | 21 | /** 22 | * Remove a handler by passing the `url` event type and the handler 23 | * 24 | * @deprecated 25 | */ 26 | static removeEventListener(type, handler) { 27 | console.warn( 28 | '"LinkingIOS.removeEventListener" is deprecated. Use "Linking.removeEventListener" instead.' 29 | ); 30 | Linking.removeEventListener(type, handler); 31 | } 32 | 33 | /** 34 | * Try to open the given `url` with any of the installed apps. 35 | * 36 | * @deprecated 37 | */ 38 | static openURL(url) { 39 | console.warn( 40 | '"LinkingIOS.openURL" is deprecated. Use the promise based "Linking.openURL" instead.' 41 | ); 42 | Linking.openURL(url); 43 | } 44 | 45 | /** 46 | * Determine whether or not an installed app can handle a given URL. 47 | * The callback function will be called with `bool supported` as the only argument 48 | * 49 | * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key 50 | * inside `Info.plist`. 51 | * 52 | * @deprecated 53 | */ 54 | static canOpenURL(url, callback) { 55 | console.warn( 56 | '"LinkingIOS.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.' 57 | ); 58 | invariant( 59 | typeof callback === 'function', 60 | 'A valid callback function is required' 61 | ); 62 | Linking.canOpenURL(url).then(callback); 63 | } 64 | 65 | /** 66 | * If the app launch was triggered by an app link, it will pop the link url, 67 | * otherwise it will return `null` 68 | * 69 | * @deprecated 70 | */ 71 | static popInitialURL() { 72 | const initialURL = _initialURL; 73 | _initialURL = null; 74 | return initialURL; 75 | } 76 | } 77 | 78 | module.exports = LinkingIOS; 79 | -------------------------------------------------------------------------------- /src/api/ListViewDataSource.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | class ListViewDataSource { 4 | constructor() { 5 | this._dataBlob = null; 6 | } 7 | 8 | getRowCount() { 9 | 10 | } 11 | 12 | cloneWithRows(data) { 13 | const newSource = new ListViewDataSource(); 14 | newSource._dataBlob = data; 15 | 16 | return newSource; 17 | } 18 | 19 | cloneWithRowsAndSections(data) { 20 | const newSource = new ListViewDataSource(); 21 | newSource._dataBlob = data; 22 | 23 | return newSource; 24 | } 25 | } 26 | 27 | module.exports = ListViewDataSource; 28 | -------------------------------------------------------------------------------- /src/api/NetInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Network/NetInfo.js 3 | */ 4 | import Platform from '../plugins/Platform'; 5 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 6 | 7 | const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange'; 8 | const _subscriptions = new Map(); 9 | 10 | let isExpensive = false; 11 | let networkInfo = { 12 | connected: true 13 | }; 14 | 15 | 16 | const NetInfo = { 17 | addEventListener(eventname, handler) { 18 | const listener = DeviceEventEmitter.addListener( 19 | DEVICE_CONNECTIVITY_EVENT, 20 | ({ network_info }) => handler(network_info) 21 | ); 22 | _subscriptions.set(handler, listener); 23 | }, 24 | 25 | removeEventListener(eventName, handler) { 26 | const listener = _subscriptions.get(handler); 27 | if (!listener) { 28 | return; 29 | } 30 | listener.remove(); 31 | _subscriptions.delete(handler); 32 | }, 33 | 34 | fetch() { 35 | return Promise.resolve(networkInfo); 36 | }, 37 | 38 | isConnected: { 39 | addEventListener(eventname, handler) { 40 | 41 | }, 42 | 43 | removeEventListener(eventName, handler) { 44 | 45 | }, 46 | fetch() { 47 | return NetInfo.fetch().then(info => info.connected); 48 | }, 49 | }, 50 | 51 | isConnectionExpensive(callback) { 52 | if (Platform.OS === 'android') { 53 | callback(isExpensive); 54 | } else { 55 | callback(null, 'Unsupported'); 56 | } 57 | }, 58 | 59 | // TODO(lmr): figure out a good way to expose setters here. 60 | __setNetworkInfo(info) { 61 | networkInfo = info; 62 | }, 63 | __setIsConnectionExpensive(expensive) { 64 | isExpensive = expensive; 65 | }, 66 | __setIsConnected(connected) { 67 | networkInfo = Object.assign({}, networkInfo, { connected }); 68 | }, 69 | }; 70 | 71 | module.exports = NetInfo; 72 | -------------------------------------------------------------------------------- /src/api/PixelRatio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/PixelRatio.js 3 | */ 4 | const PixelRatio = { 5 | get() { 6 | return 2; 7 | }, 8 | getFontScale() { 9 | return 2; 10 | }, 11 | getPixelSizeForLayoutSize(layoutSize) { 12 | return Math.round(layoutSize * PixelRatio.get()); 13 | }, 14 | roundToNearestPixel(layoutSize) { 15 | const ratio = PixelRatio.get(); 16 | return Math.round(layoutSize * ratio) / ratio; 17 | }, 18 | startDetecting() { 19 | 20 | }, 21 | }; 22 | 23 | module.exports = PixelRatio; 24 | -------------------------------------------------------------------------------- /src/api/PushNotificationIOS.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 3 | 4 | const _notifHandlers = new Map(); 5 | let _initialNotification = null; 6 | 7 | const DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; 8 | const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; 9 | 10 | class PushNotificationIOS { 11 | /** 12 | * Schedules the localNotification for immediate presentation. 13 | * 14 | * details is an object containing: 15 | * 16 | * - `alertBody` : The message displayed in the notification alert. 17 | * - `soundName` : The sound played when the notification is fired (optional). 18 | * 19 | */ 20 | static presentLocalNotification(details) { 21 | 22 | } 23 | 24 | /** 25 | * Schedules the localNotification for future presentation. 26 | * 27 | * details is an object containing: 28 | * 29 | * - `fireDate` : The date and time when the system should deliver the notification. 30 | * - `alertBody` : The message displayed in the notification alert. 31 | * - `soundName` : The sound played when the notification is fired (optional). 32 | * 33 | */ 34 | static scheduleLocalNotification(details) { 35 | 36 | } 37 | 38 | /** 39 | * Cancels all scheduled localNotifications 40 | */ 41 | static cancelAllLocalNotifications() { 42 | 43 | } 44 | 45 | /** 46 | * Sets the badge number for the app icon on the home screen 47 | */ 48 | static setApplicationIconBadgeNumber(number) { 49 | 50 | } 51 | 52 | /** 53 | * Gets the current badge number for the app icon on the home screen 54 | */ 55 | static getApplicationIconBadgeNumber(callback) { 56 | 57 | } 58 | 59 | /** 60 | * Attaches a listener to remote notification events while the app is running 61 | * in the foreground or the background. 62 | * 63 | * Valid events are: 64 | * 65 | * - `notification` : Fired when a remote notification is received. The 66 | * handler will be invoked with an instance of `PushNotificationIOS`. 67 | * - `register`: Fired when the user registers for remote notifications. The 68 | * handler will be invoked with a hex string representing the deviceToken. 69 | */ 70 | static addEventListener(type, handler) { 71 | invariant( 72 | type === 'notification' || type === 'register', 73 | 'PushNotificationIOS only supports `notification` and `register` events' 74 | ); 75 | let listener; 76 | if (type === 'notification') { 77 | listener = DeviceEventEmitter.addListener( 78 | DEVICE_NOTIF_EVENT, 79 | (notifData) => { 80 | handler(new PushNotificationIOS(notifData)); 81 | } 82 | ); 83 | } else if (type === 'register') { 84 | listener = DeviceEventEmitter.addListener( 85 | NOTIF_REGISTER_EVENT, 86 | (registrationInfo) => { 87 | handler(registrationInfo.deviceToken); 88 | } 89 | ); 90 | } 91 | _notifHandlers.set(handler, listener); 92 | } 93 | 94 | /** 95 | * Requests notification permissions from iOS, prompting the user's 96 | * dialog box. By default, it will request all notification permissions, but 97 | * a subset of these can be requested by passing a map of requested 98 | * permissions. 99 | * The following permissions are supported: 100 | * 101 | * - `alert` 102 | * - `badge` 103 | * - `sound` 104 | * 105 | * If a map is provided to the method, only the permissions with truthy values 106 | * will be requested. 107 | */ 108 | static requestPermissions(permissions) { 109 | 110 | } 111 | 112 | /** 113 | * Unregister for all remote notifications received via Apple Push Notification service. 114 | * 115 | * You should call this method in rare circumstances only, such as when a new version of 116 | * the app removes support for all types of remote notifications. Users can temporarily 117 | * prevent apps from receiving remote notifications through the Notifications section of 118 | * the Settings app. Apps unregistered through this method can always re-register. 119 | */ 120 | static abandonPermissions() { 121 | 122 | } 123 | 124 | /** 125 | * See what push permissions are currently enabled. `callback` will be 126 | * invoked with a `permissions` object: 127 | * 128 | * - `alert` :boolean 129 | * - `badge` :boolean 130 | * - `sound` :boolean 131 | */ 132 | static checkPermissions(callback) { 133 | invariant( 134 | typeof callback === 'function', 135 | 'Must provide a valid callback' 136 | ); 137 | } 138 | 139 | /** 140 | * Removes the event listener. Do this in `componentWillUnmount` to prevent 141 | * memory leaks 142 | */ 143 | static removeEventListener(type, handler) { 144 | invariant( 145 | type === 'notification' || type === 'register', 146 | 'PushNotificationIOS only supports `notification` and `register` events' 147 | ); 148 | const listener = _notifHandlers.get(handler); 149 | if (!listener) { 150 | return; 151 | } 152 | listener.remove(); 153 | _notifHandlers.delete(handler); 154 | } 155 | 156 | /** 157 | * An initial notification will be available if the app was cold-launched 158 | * from a notification. 159 | * 160 | * The first caller of `popInitialNotification` will get the initial 161 | * notification object, or `null`. Subsequent invocations will return null. 162 | */ 163 | static popInitialNotification() { 164 | const initialNotification = _initialNotification && 165 | new PushNotificationIOS(_initialNotification); 166 | _initialNotification = null; 167 | return initialNotification; 168 | } 169 | 170 | /** 171 | * You will never need to instantiate `PushNotificationIOS` yourself. 172 | * Listening to the `notification` event and invoking 173 | * `popInitialNotification` is sufficient 174 | */ 175 | constructor(nativeNotif) { 176 | this._data = {}; 177 | 178 | // Extract data from Apple's `aps` dict as defined: 179 | 180 | // https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html 181 | 182 | Object.keys(nativeNotif).forEach((notifKey) => { 183 | const notifVal = nativeNotif[notifKey]; 184 | if (notifKey === 'aps') { 185 | this._alert = notifVal.alert; 186 | this._sound = notifVal.sound; 187 | this._badgeCount = notifVal.badge; 188 | } else { 189 | this._data[notifKey] = notifVal; 190 | } 191 | }); 192 | } 193 | 194 | /** 195 | * An alias for `getAlert` to get the notification's main message string 196 | */ 197 | getMessage() { 198 | // alias because "alert" is an ambiguous name 199 | return this._alert; 200 | } 201 | 202 | /** 203 | * Gets the sound string from the `aps` object 204 | */ 205 | getSound() { 206 | return this._sound; 207 | } 208 | 209 | /** 210 | * Gets the notification's main message from the `aps` object 211 | */ 212 | getAlert() { 213 | return this._alert; 214 | } 215 | 216 | /** 217 | * Gets the badge count number from the `aps` object 218 | */ 219 | getBadgeCount() { 220 | return this._badgeCount; 221 | } 222 | 223 | /** 224 | * Gets the data object on the notif 225 | */ 226 | getData() { 227 | return this._data; 228 | } 229 | } 230 | 231 | module.exports = PushNotificationIOS; 232 | -------------------------------------------------------------------------------- /src/api/Settings.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import DeviceEventEmitter from '../plugins/DeviceEventEmitter'; 3 | 4 | const subscriptions = []; 5 | 6 | const Settings = { 7 | _settings: {}, 8 | 9 | get(key) { 10 | return this._settings[key]; 11 | }, 12 | 13 | set(settings) { 14 | this._settings = Object.assign(this._settings, settings); 15 | }, 16 | 17 | watchKeys(keys, callback) { 18 | let newKeys = keys; 19 | if (typeof keys === 'string') { 20 | newKeys = [keys]; 21 | } 22 | 23 | invariant( 24 | Array.isArray(newKeys), 25 | 'keys should be a string or array of strings' 26 | ); 27 | 28 | const sid = subscriptions.length; 29 | subscriptions.push({ keys: newKeys, callback }); 30 | return sid; 31 | }, 32 | 33 | clearWatch(watchId) { 34 | if (watchId < subscriptions.length) { 35 | subscriptions[watchId] = { 36 | keys: [], 37 | callback: null, 38 | }; 39 | } 40 | }, 41 | 42 | _sendObservations(body) { 43 | Object.keys(body).forEach((key) => { 44 | const newValue = body[key]; 45 | const didChange = this._settings[key] !== newValue; 46 | this._settings[key] = newValue; 47 | 48 | if (didChange) { 49 | subscriptions.forEach((sub) => { 50 | if (sub.keys.indexOf(key) !== -1 && sub.callback) { 51 | sub.callback(); 52 | } 53 | }); 54 | } 55 | }); 56 | }, 57 | 58 | __emulateDeviceSettingsChange(settings) { 59 | DeviceEventEmitter.emit('settingsUpdated', settings); 60 | }, 61 | }; 62 | 63 | DeviceEventEmitter.addListener( 64 | 'settingsUpdated', 65 | Settings._sendObservations.bind(Settings) 66 | ); 67 | 68 | module.exports = Settings; 69 | -------------------------------------------------------------------------------- /src/api/Share.js: -------------------------------------------------------------------------------- 1 | 2 | class Share { 3 | 4 | static share(content, options) { 5 | return Promise.resolve('sharedAction'); 6 | } 7 | 8 | static get sharedAction() { 9 | return 'sharedAction'; 10 | } 11 | 12 | static get dismissedAction() { 13 | return 'dismissedAction'; 14 | } 15 | } 16 | 17 | module.exports = Share; 18 | -------------------------------------------------------------------------------- /src/api/StatusBarIOS.js: -------------------------------------------------------------------------------- 1 | let _style = {}; 2 | let _hidden = false; 3 | let _networkActivityIndicatorVisible = true; 4 | 5 | const StatusBarIOS = { 6 | 7 | setStyle(style, animated) { 8 | _style = style; 9 | }, 10 | 11 | setHidden(hidden, animation) { 12 | _hidden = hidden; 13 | }, 14 | 15 | setNetworkActivityIndicatorVisible(visible) { 16 | _networkActivityIndicatorVisible = visible; 17 | }, 18 | 19 | __getStyle() { 20 | return _style; 21 | }, 22 | 23 | __getHidden() { 24 | return _hidden; 25 | }, 26 | 27 | __getNetworkActivityIndicatorVisible() { 28 | return _networkActivityIndicatorVisible; 29 | }, 30 | }; 31 | 32 | module.exports = StatusBarIOS; 33 | -------------------------------------------------------------------------------- /src/api/StyleSheet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheet.js 3 | */ 4 | const StyleSheet = { 5 | create(styles) { 6 | return styles; 7 | }, 8 | flatten(styles) { 9 | if (Array.isArray(styles)) { 10 | return Object.assign({}, ...styles.map(StyleSheet.flatten)); 11 | } 12 | 13 | return styles; 14 | } 15 | }; 16 | 17 | module.exports = StyleSheet; 18 | -------------------------------------------------------------------------------- /src/api/TextInputState.js: -------------------------------------------------------------------------------- 1 | 2 | const TextInputState = { 3 | /** 4 | * Internal state 5 | */ 6 | _currentlyFocusedID: null, 7 | 8 | /** 9 | * Returns the ID of the currently focused text field, if one exists 10 | * If no text field is focused it returns null 11 | */ 12 | currentlyFocusedField() { 13 | return this._currentlyFocusedID; 14 | }, 15 | 16 | /** 17 | * @param {number} TextInputID id of the text field to focus 18 | * Focuses the specified text field 19 | * noop if the text field was already focused 20 | */ 21 | focusTextInput(textFieldID) { 22 | if (this._currentlyFocusedID !== textFieldID && textFieldID !== null) { 23 | this._currentlyFocusedID = textFieldID; 24 | } 25 | }, 26 | 27 | /** 28 | * @param {number} textFieldID id of the text field to focus 29 | * Unfocuses the specified text field 30 | * noop if it wasn't focused 31 | */ 32 | blurTextInput(textFieldID) { 33 | if (this._currentlyFocusedID === textFieldID && textFieldID !== null) { 34 | this._currentlyFocusedID = null; 35 | } 36 | } 37 | }; 38 | 39 | module.exports = TextInputState; 40 | -------------------------------------------------------------------------------- /src/api/TimePickerAndroid.js: -------------------------------------------------------------------------------- 1 | import TimePickerModule from '../NativeModules/TimePickerAndroid'; 2 | 3 | /** 4 | * Opens the standard Android time picker dialog. 5 | * 6 | * ### Example 7 | * 8 | * ``` 9 | * try { 10 | * const {action, hour, minute} = await TimePickerAndroid.open({ 11 | * hour: 14, 12 | * minute: 0, 13 | * is24Hour: false, // Will display '2 PM' 14 | * }); 15 | * if (action !== DatePickerAndroid.dismissedAction) { 16 | * // Selected hour (0-23), minute (0-59) 17 | * } 18 | * } catch ({code, message}) { 19 | * console.warn('Cannot open time picker', message); 20 | * } 21 | * ``` 22 | */ 23 | class TimePickerAndroid { 24 | 25 | /** 26 | * Opens the standard Android time picker dialog. 27 | * 28 | * The available keys for the `options` object are: 29 | * * `hour` (0-23) - the hour to show, defaults to the current time 30 | * * `minute` (0-59) - the minute to show, defaults to the current time 31 | * * `is24Hour` (boolean) - If `true`, the picker uses the 24-hour format. If `false`, 32 | * the picker shows an AM/PM chooser. If undefined, the default for the current locale 33 | * is used. 34 | * 35 | * Returns a Promise which will be invoked an object containing `action`, `hour` (0-23), 36 | * `minute` (0-59) if the user picked a time. If the user dismissed the dialog, the Promise will 37 | * still be resolved with action being `TimePickerAndroid.dismissedAction` and all the other keys 38 | * being undefined. **Always** check whether the `action` before reading the values. 39 | */ 40 | static open(options) { 41 | return TimePickerModule.open(options); 42 | } 43 | 44 | /** 45 | * A time has been selected. 46 | */ 47 | static get timeSetAction() { return 'timeSetAction'; } 48 | /** 49 | * The dialog has been dismissed. 50 | */ 51 | static get dismissedAction() { return 'dismissedAction'; } 52 | } 53 | 54 | module.exports = TimePickerAndroid; 55 | -------------------------------------------------------------------------------- /src/api/TouchHistoryMath.js: -------------------------------------------------------------------------------- 1 | const TouchHistoryMath = { 2 | /** 3 | * This code is optimized and not intended to look beautiful. This allows 4 | * computing of touch centroids that have moved after `touchesChangedAfter` 5 | * timeStamp. You can compute the current centroid involving all touches 6 | * moves after `touchesChangedAfter`, or you can compute the previous 7 | * centroid of all touches that were moved after `touchesChangedAfter`. 8 | * 9 | * @param {TouchHistoryMath} touchHistory Standard Responder touch track 10 | * data. 11 | * @param {number} touchesChangedAfter timeStamp after which moved touches 12 | * are considered "actively moving" - not just "active". 13 | * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension. 14 | * @param {boolean} ofCurrent Compute current centroid for actively moving 15 | * touches vs. previous centroid of now actively moving touches. 16 | * @return {number} value of centroid in specified dimension. 17 | */ 18 | centroidDimension(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) { 19 | const touchBank = touchHistory.touchBank; 20 | let total = 0; 21 | let count = 0; 22 | 23 | const oneTouchData = touchHistory.numberActiveTouches === 1 ? 24 | touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null; 25 | 26 | if (oneTouchData !== null) { 27 | if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) { 28 | // FIXME: DONT USE TERNARIES!!!! 29 | total += 30 | ofCurrent && isXAxis ? oneTouchData.currentPageX : // eslint-disable-line 31 | ofCurrent && !isXAxis ? oneTouchData.currentPageY : // eslint-disable-line 32 | !ofCurrent && isXAxis ? oneTouchData.previousPageX : 33 | oneTouchData.previousPageY; 34 | count = 1; 35 | } 36 | } else { 37 | for (let i = 0; i < touchBank.length; i++) { 38 | const touchTrack = touchBank[i]; 39 | if (touchTrack !== null && 40 | touchTrack !== undefined && 41 | touchTrack.touchActive && 42 | touchTrack.currentTimeStamp >= touchesChangedAfter) { 43 | let toAdd; // Yuck, program temporarily in invalid state. 44 | if (ofCurrent && isXAxis) { 45 | toAdd = touchTrack.currentPageX; 46 | } else if (ofCurrent && !isXAxis) { 47 | toAdd = touchTrack.currentPageY; 48 | } else if (!ofCurrent && isXAxis) { 49 | toAdd = touchTrack.previousPageX; 50 | } else { 51 | toAdd = touchTrack.previousPageY; 52 | } 53 | total += toAdd; 54 | count++; 55 | } 56 | } 57 | } 58 | return count > 0 ? total / count : TouchHistoryMath.noCentroid; 59 | }, 60 | 61 | currentCentroidXOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { 62 | return TouchHistoryMath.centroidDimension( 63 | touchHistory, 64 | touchesChangedAfter, 65 | true, // isXAxis 66 | true // ofCurrent 67 | ); 68 | }, 69 | 70 | currentCentroidYOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { 71 | return TouchHistoryMath.centroidDimension( 72 | touchHistory, 73 | touchesChangedAfter, 74 | false, // isXAxis 75 | true // ofCurrent 76 | ); 77 | }, 78 | 79 | previousCentroidXOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { 80 | return TouchHistoryMath.centroidDimension( 81 | touchHistory, 82 | touchesChangedAfter, 83 | true, // isXAxis 84 | false // ofCurrent 85 | ); 86 | }, 87 | 88 | previousCentroidYOfTouchesChangedAfter(touchHistory, touchesChangedAfter) { 89 | return TouchHistoryMath.centroidDimension( 90 | touchHistory, 91 | touchesChangedAfter, 92 | false, // isXAxis 93 | false // ofCurrent 94 | ); 95 | }, 96 | 97 | currentCentroidX(touchHistory) { 98 | return TouchHistoryMath.centroidDimension( 99 | touchHistory, 100 | 0, // touchesChangedAfter 101 | true, // isXAxis 102 | true // ofCurrent 103 | ); 104 | }, 105 | 106 | currentCentroidY(touchHistory) { 107 | return TouchHistoryMath.centroidDimension( 108 | touchHistory, 109 | 0, // touchesChangedAfter 110 | false, // isXAxis 111 | true // ofCurrent 112 | ); 113 | }, 114 | 115 | noCentroid: -1, 116 | }; 117 | 118 | module.exports = TouchHistoryMath; 119 | -------------------------------------------------------------------------------- /src/api/VibrationIOS.js: -------------------------------------------------------------------------------- 1 | import NativeVibration from '../NativeModules/Vibration'; 2 | import invariant from 'invariant'; 3 | 4 | /** 5 | * The Vibration API is exposed at `VibrationIOS.vibrate()`. On iOS, calling this 6 | * function will trigger a one second vibration. The vibration is asynchronous 7 | * so this method will return immediately. 8 | * 9 | * There will be no effect on devices that do not support Vibration, eg. the iOS 10 | * simulator. 11 | * 12 | * Vibration patterns are currently unsupported. 13 | */ 14 | 15 | const Vibration = { 16 | vibrate() { 17 | invariant( 18 | arguments[0] === undefined, 19 | 'Vibration patterns not supported.' 20 | ); 21 | NativeVibration.vibrate(); 22 | } 23 | }; 24 | 25 | module.exports = Vibration; 26 | -------------------------------------------------------------------------------- /src/components/ART/Path.js: -------------------------------------------------------------------------------- 1 | 2 | class Path { 3 | constructor(path) { 4 | [ 5 | 'push', 6 | 'reset', 7 | 'move', 8 | 'moveTo', 9 | 'line', 10 | 'lineTo', 11 | 'curve', 12 | 'curveTo', 13 | 'arc', 14 | 'arcTo', 15 | 'counterArc', 16 | 'counterArcTo', 17 | 'close' 18 | ].forEach((methodName) => { this[methodName] = () => this; }); 19 | 20 | this.path = path || []; 21 | } 22 | 23 | toJSON() { 24 | return JSON.stringify(this.path); 25 | } 26 | } 27 | 28 | module.exports = Path; 29 | -------------------------------------------------------------------------------- /src/components/ART/Transform.js: -------------------------------------------------------------------------------- 1 | 2 | class Transform { 3 | constructor() { 4 | this.xx = 0; 5 | this.yx = 0; 6 | this.xy = 0; 7 | this.yy = 0; 8 | this.x = 0; 9 | this.y = 0; 10 | } 11 | transformTo() { 12 | return this; 13 | } 14 | move() { 15 | return this; 16 | } 17 | rotate() { 18 | return this; 19 | } 20 | scale() { 21 | return this; 22 | } 23 | transform() { 24 | return this; 25 | } 26 | } 27 | 28 | module.exports = Transform; 29 | -------------------------------------------------------------------------------- /src/components/ART/index.js: -------------------------------------------------------------------------------- 1 | import createMockComponent from '../createMockComponent'; 2 | import Transform from './Transform'; 3 | import Path from './Path'; 4 | 5 | const LINEAR_GRADIENT = 1; 6 | const RADIAL_GRADIENT = 2; 7 | const PATTERN = 3; 8 | 9 | function CSSBackgroundPattern() { 10 | // TODO(lmr): 11 | return {}; 12 | } 13 | 14 | function Pattern(url, width, height, left, top) { 15 | this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height]; 16 | } 17 | 18 | function LinearGradient(stops, x1, y1, x2, y2) { 19 | this._brush = [LINEAR_GRADIENT, +x1, +y1, +x2, +y2]; 20 | } 21 | 22 | function RadialGradient(stops, fx, fy, rx, ry, cx, cy) { 23 | this._brush = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy]; 24 | } 25 | 26 | const ReactART = { 27 | LinearGradient, 28 | RadialGradient, 29 | Pattern, 30 | Transform, 31 | Path, 32 | Surface: createMockComponent('Surface'), 33 | Group: createMockComponent('Group'), 34 | ClippingRectangle: createMockComponent('ClippingRectangle'), 35 | Shape: createMockComponent('Shape'), 36 | Text: createMockComponent('Text'), 37 | CSSBackgroundPattern, 38 | }; 39 | 40 | module.exports = ReactART; 41 | -------------------------------------------------------------------------------- /src/components/ActivityIndicator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/ActivityIndicator/ActivityIndicator.js 3 | */ 4 | import React from 'react'; 5 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 6 | import View from './View'; 7 | import ColorPropType from '../propTypes/ColorPropType'; 8 | 9 | const { PropTypes } = React; 10 | 11 | const ActivityIndicator = React.createClass({ 12 | propTypes: { 13 | ...View.propTypes, 14 | /** 15 | * Whether to show the indicator (true, the default) or hide it (false). 16 | */ 17 | animating: PropTypes.bool, 18 | /** 19 | * The foreground color of the spinner (default is gray). 20 | */ 21 | color: ColorPropType, 22 | /** 23 | * Whether the indicator should hide when not animating (true by default). 24 | */ 25 | hidesWhenStopped: PropTypes.bool, 26 | /** 27 | * Size of the indicator. Small has a height of 20, large has a height of 36. 28 | */ 29 | size: PropTypes.oneOf([ 30 | 'small', 31 | 'large', 32 | ]), 33 | /** 34 | * Invoked on mount and layout changes with 35 | * 36 | * {nativeEvent: { layout: {x, y, width, height}}}. 37 | */ 38 | onLayout: PropTypes.func, 39 | }, 40 | mixins: [NativeMethodsMixin], 41 | render() { 42 | return null; 43 | }, 44 | }); 45 | 46 | module.exports = ActivityIndicator; 47 | -------------------------------------------------------------------------------- /src/components/ActivityIndicatorIOS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/ActivityIndicator/ActivityIndicatorIOS.ios.js 3 | */ 4 | import React from 'react'; 5 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 6 | import View from './View'; 7 | 8 | const { PropTypes } = React; 9 | 10 | const ActivityIndicatorIOS = React.createClass({ 11 | propTypes: { 12 | ...View.propTypes, 13 | /** 14 | * Whether to show the indicator (true, the default) or hide it (false). 15 | */ 16 | animating: PropTypes.bool, 17 | /** 18 | * The foreground color of the spinner (default is gray). 19 | */ 20 | color: PropTypes.string, 21 | /** 22 | * Whether the indicator should hide when not animating (true by default). 23 | */ 24 | hidesWhenStopped: PropTypes.bool, 25 | /** 26 | * Size of the indicator. Small has a height of 20, large has a height of 36. 27 | */ 28 | size: PropTypes.oneOf([ 29 | 'small', 30 | 'large', 31 | ]), 32 | /** 33 | * Invoked on mount and layout changes with 34 | * 35 | * {nativeEvent: { layout: {x, y, width, height}}}. 36 | */ 37 | onLayout: PropTypes.func, 38 | }, 39 | 40 | mixins: [NativeMethodsMixin], 41 | 42 | render() { 43 | return null; 44 | }, 45 | }); 46 | 47 | module.exports = ActivityIndicatorIOS; 48 | -------------------------------------------------------------------------------- /src/components/DrawerLayoutAndroid.js: -------------------------------------------------------------------------------- 1 | /** 2 | *https://github.com/facebook/react-native/blob/master/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js 3 | */ 4 | import React from 'react'; 5 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 6 | import View from './View'; 7 | import UIManager from '../NativeModules/UIManager'; 8 | import ColorPropType from '../propTypes/ColorPropType'; 9 | 10 | const ReactPropTypes = React.PropTypes; 11 | const DrawerConsts = UIManager.AndroidDrawerLayout.Constants; 12 | 13 | const DrawerLayoutAndroid = React.createClass({ 14 | 15 | propTypes: { 16 | ...View.propTypes, 17 | /** 18 | * Determines whether the keyboard gets dismissed in response to a drag. 19 | * - 'none' (the default), drags do not dismiss the keyboard. 20 | * - 'on-drag', the keyboard is dismissed when a drag begins. 21 | */ 22 | keyboardDismissMode: ReactPropTypes.oneOf([ 23 | 'none', // default 24 | 'on-drag', 25 | ]), 26 | /** 27 | * Specifies the background color of the drawer. The default value is white. 28 | * If you want to set the opacity of the drawer, use rgba. Example: 29 | * 30 | * ``` 31 | * return ( 32 | * 33 | * 34 | * ); 35 | * ``` 36 | */ 37 | drawerBackgroundColor: ColorPropType, 38 | /** 39 | * Specifies the side of the screen from which the drawer will slide in. 40 | */ 41 | drawerPosition: ReactPropTypes.oneOf([ 42 | DrawerConsts.DrawerPosition.Left, 43 | DrawerConsts.DrawerPosition.Right 44 | ]), 45 | /** 46 | * Specifies the width of the drawer, more precisely the width of the view that be pulled in 47 | * from the edge of the window. 48 | */ 49 | drawerWidth: ReactPropTypes.number, 50 | /** 51 | * Specifies the lock mode of the drawer. The drawer can be locked in 3 states: 52 | * - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures. 53 | * - locked-closed, meaning that the drawer will stay closed and not respond to gestures. 54 | * - locked-open, meaning that the drawer will stay opened and not respond to gestures. 55 | * The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`). 56 | */ 57 | drawerLockMode: ReactPropTypes.oneOf([ 58 | 'unlocked', 59 | 'locked-closed', 60 | 'locked-open' 61 | ]), 62 | /** 63 | * Function called whenever there is an interaction with the navigation view. 64 | */ 65 | onDrawerSlide: ReactPropTypes.func, 66 | /** 67 | * Function called when the drawer state has changed. The drawer can be in 3 states: 68 | * - idle, meaning there is no interaction with the navigation view happening at the time 69 | * - dragging, meaning there is currently an interaction with the navigation view 70 | * - settling, meaning that there was an interaction with the navigation view, and the 71 | * navigation view is now finishing its closing or opening animation 72 | */ 73 | onDrawerStateChanged: ReactPropTypes.func, 74 | /** 75 | * Function called whenever the navigation view has been opened. 76 | */ 77 | onDrawerOpen: ReactPropTypes.func, 78 | /** 79 | * Function called whenever the navigation view has been closed. 80 | */ 81 | onDrawerClose: ReactPropTypes.func, 82 | /** 83 | * The navigation view that will be rendered to the side of the screen and can be pulled in. 84 | */ 85 | renderNavigationView: ReactPropTypes.func.isRequired, 86 | 87 | /** 88 | * Make the drawer take the entire screen and draw the background of the 89 | * status bar to allow it to open over the status bar. It will only have an 90 | * effect on API 21+. 91 | */ 92 | statusBarBackgroundColor: ColorPropType, 93 | }, 94 | 95 | mixins: [NativeMethodsMixin], 96 | 97 | statics: { 98 | positions: DrawerConsts.DrawerPosition 99 | }, 100 | 101 | openDrawer() { 102 | // do nothing 103 | }, 104 | 105 | closeDrawer() { 106 | // do nothing 107 | }, 108 | 109 | render() { 110 | return null; 111 | } 112 | 113 | }); 114 | 115 | module.exports = DrawerLayoutAndroid; 116 | -------------------------------------------------------------------------------- /src/components/Image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Image/Image.ios.js 3 | */ 4 | import React from 'react'; 5 | import styleSheetPropType from '../propTypes/StyleSheetPropType'; 6 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 7 | import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; 8 | import ImageStylePropTypes from '../propTypes/ImageStylePropTypes'; 9 | import ImageResizeMode from '../propTypes/ImageResizeMode'; 10 | 11 | const { PropTypes } = React; 12 | 13 | const Image = React.createClass({ 14 | propTypes: { 15 | style: styleSheetPropType(ImageStylePropTypes), 16 | /** 17 | * `uri` is a string representing the resource identifier for the image, which 18 | * could be an http address, a local file path, or the name of a static image 19 | * resource (which should be wrapped in the `require('./path/to/image.png')` function). 20 | */ 21 | source: PropTypes.oneOfType([ 22 | PropTypes.shape({ 23 | uri: PropTypes.string, 24 | }), 25 | // Opaque type returned by require('./image.jpg') 26 | PropTypes.number, 27 | ]), 28 | /** 29 | * A static image to display while loading the image source. 30 | * @platform ios 31 | */ 32 | defaultSource: PropTypes.oneOfType([ 33 | PropTypes.shape({ 34 | uri: PropTypes.string, 35 | }), 36 | // Opaque type returned by require('./image.jpg') 37 | PropTypes.number, 38 | ]), 39 | /** 40 | * When true, indicates the image is an accessibility element. 41 | * @platform ios 42 | */ 43 | accessible: PropTypes.bool, 44 | /** 45 | * The text that's read by the screen reader when the user interacts with 46 | * the image. 47 | * @platform ios 48 | */ 49 | accessibilityLabel: PropTypes.string, 50 | /** 51 | * When the image is resized, the corners of the size specified 52 | * by capInsets will stay a fixed size, but the center content and borders 53 | * of the image will be stretched. This is useful for creating resizable 54 | * rounded buttons, shadows, and other resizable assets. More info on 55 | * [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets) 56 | * @platform ios 57 | */ 58 | capInsets: EdgeInsetsPropType, 59 | /** 60 | * Determines how to resize the image when the frame doesn't match the raw 61 | * image dimensions. 62 | * 63 | * 'cover': Scale the image uniformly (maintain the image's aspect ratio) 64 | * so that both dimensions (width and height) of the image will be equal 65 | * to or larger than the corresponding dimension of the view (minus padding). 66 | * 67 | * 'contain': Scale the image uniformly (maintain the image's aspect ratio) 68 | * so that both dimensions (width and height) of the image will be equal to 69 | * or less than the corresponding dimension of the view (minus padding). 70 | * 71 | * 'stretch': Scale width and height independently, This may change the 72 | * aspect ratio of the src. 73 | * 74 | * `repeat`: Repeat the image to cover the frame of the view. The 75 | * image will keep it's size and aspect ratio. (iOS only) 76 | * 77 | */ 78 | resizeMode: PropTypes.oneOf(ImageResizeMode), 79 | /** 80 | * A unique identifier for this element to be used in UI Automation 81 | * testing scripts. 82 | */ 83 | testID: PropTypes.string, 84 | /** 85 | * Invoked on mount and layout changes with 86 | * `{nativeEvent: {layout: {x, y, width, height}}}`. 87 | */ 88 | onLayout: PropTypes.func, 89 | /** 90 | * Invoked on load start 91 | */ 92 | onLoadStart: PropTypes.func, 93 | /** 94 | * Invoked on download progress with `{nativeEvent: {loaded, total}}` 95 | * @platform ios 96 | */ 97 | onProgress: PropTypes.func, 98 | /** 99 | * Invoked on load error with `{nativeEvent: {error}}` 100 | * @platform ios 101 | */ 102 | onError: PropTypes.func, 103 | /** 104 | * Invoked when load completes successfully 105 | */ 106 | onLoad: PropTypes.func, 107 | /** 108 | * Invoked when load either succeeds or fails 109 | */ 110 | onLoadEnd: PropTypes.func, 111 | }, 112 | mixins: [NativeMethodsMixin], 113 | statics: { 114 | resizeMode: ImageResizeMode, 115 | getSize(uri, success, failure) { 116 | 117 | }, 118 | prefetch(uri) { 119 | 120 | } 121 | }, 122 | render() { 123 | return null; 124 | }, 125 | }); 126 | 127 | module.exports = Image; 128 | -------------------------------------------------------------------------------- /src/components/ListView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ScrollResponder from '../mixins/ScrollResponder'; 3 | import TimerMixin from 'react-timer-mixin'; 4 | import ScrollView from './ScrollView'; 5 | import ListViewDataSource from '../api/ListViewDataSource'; 6 | 7 | const { PropTypes } = React; 8 | const SCROLLVIEW_REF = 'listviewscroll'; 9 | 10 | 11 | const ListView = React.createClass({ 12 | propTypes: { 13 | ...ScrollView.propTypes, 14 | 15 | dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, 16 | /** 17 | * (sectionID, rowID, adjacentRowHighlighted) => renderable 18 | * 19 | * If provided, a renderable component to be rendered as the separator 20 | * below each row but not the last row if there is a section header below. 21 | * Take a sectionID and rowID of the row above and whether its adjacent row 22 | * is highlighted. 23 | */ 24 | renderSeparator: PropTypes.func, 25 | /** 26 | * (rowData, sectionID, rowID, highlightRow) => renderable 27 | * 28 | * Takes a data entry from the data source and its ids and should return 29 | * a renderable component to be rendered as the row. By default the data 30 | * is exactly what was put into the data source, but it's also possible to 31 | * provide custom extractors. ListView can be notified when a row is 32 | * being highlighted by calling highlightRow function. The separators above and 33 | * below will be hidden when a row is highlighted. The highlighted state of 34 | * a row can be reset by calling highlightRow(null). 35 | */ 36 | renderRow: PropTypes.func.isRequired, 37 | /** 38 | * How many rows to render on initial component mount. Use this to make 39 | * it so that the first screen worth of data appears at one time instead of 40 | * over the course of multiple frames. 41 | */ 42 | initialListSize: PropTypes.number, 43 | /** 44 | * Called when all rows have been rendered and the list has been scrolled 45 | * to within onEndReachedThreshold of the bottom. The native scroll 46 | * event is provided. 47 | */ 48 | onEndReached: PropTypes.func, 49 | /** 50 | * Threshold in pixels for onEndReached. 51 | */ 52 | onEndReachedThreshold: PropTypes.number, 53 | /** 54 | * Number of rows to render per event loop. 55 | */ 56 | pageSize: PropTypes.number, 57 | /** 58 | * () => renderable 59 | * 60 | * The header and footer are always rendered (if these props are provided) 61 | * on every render pass. If they are expensive to re-render, wrap them 62 | * in StaticContainer or other mechanism as appropriate. Footer is always 63 | * at the bottom of the list, and header at the top, on every render pass. 64 | */ 65 | renderFooter: PropTypes.func, 66 | renderHeader: PropTypes.func, 67 | /** 68 | * (sectionData, sectionID) => renderable 69 | * 70 | * If provided, a sticky header is rendered for this section. The sticky 71 | * behavior means that it will scroll with the content at the top of the 72 | * section until it reaches the top of the screen, at which point it will 73 | * stick to the top until it is pushed off the screen by the next section 74 | * header. 75 | */ 76 | renderSectionHeader: PropTypes.func, 77 | /** 78 | * (props) => renderable 79 | * 80 | * A function that returns the scrollable component in which the list rows 81 | * are rendered. Defaults to returning a ScrollView with the given props. 82 | */ 83 | renderScrollComponent: React.PropTypes.func.isRequired, 84 | /** 85 | * How early to start rendering rows before they come on screen, in 86 | * pixels. 87 | */ 88 | scrollRenderAheadDistance: React.PropTypes.number, 89 | /** 90 | * (visibleRows, changedRows) => void 91 | * 92 | * Called when the set of visible rows changes. `visibleRows` maps 93 | * { sectionID: { rowID: true }} for all the visible rows, and 94 | * `changedRows` maps { sectionID: { rowID: true | false }} for the rows 95 | * that have changed their visibility, with true indicating visible, and 96 | * false indicating the view has moved out of view. 97 | */ 98 | onChangeVisibleRows: React.PropTypes.func, 99 | /** 100 | * A performance optimization for improving scroll perf of 101 | * large lists, used in conjunction with overflow: 'hidden' on the row 102 | * containers. This is enabled by default. 103 | */ 104 | removeClippedSubviews: React.PropTypes.bool, 105 | /** 106 | * An array of child indices determining which children get docked to the 107 | * top of the screen when scrolling. For example, passing 108 | * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the 109 | * top of the scroll view. This property is not supported in conjunction 110 | * with `horizontal={true}`. 111 | * @platform ios 112 | */ 113 | stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), 114 | }, 115 | mixins: [ScrollResponder.Mixin, TimerMixin], 116 | 117 | statics: { 118 | DataSource: ListViewDataSource, 119 | }, 120 | 121 | /** 122 | * Exports some data, e.g. for perf investigations or analytics. 123 | */ 124 | getMetrics() { // eslint-disable-line react/sort-comp 125 | // It's fixed, but the linter doesnt want to recognise it... 126 | return { 127 | contentLength: this.scrollProperties.contentLength, 128 | totalRows: this.props.dataSource.getRowCount(), 129 | renderedRows: this.state.curRenderedRowsCount, 130 | visibleRows: Object.keys(this._visibleRows).length, 131 | }; 132 | }, 133 | 134 | scrollTo(destY, destX) { 135 | this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0); 136 | }, 137 | 138 | /** 139 | * Provides a handle to the underlying scroll responder to support operations 140 | * such as scrollTo. 141 | */ 142 | getScrollResponder() { 143 | return this.refs[SCROLLVIEW_REF] && 144 | this.refs[SCROLLVIEW_REF].getScrollResponder && 145 | this.refs[SCROLLVIEW_REF].getScrollResponder(); 146 | }, 147 | 148 | setNativeProps(props) { 149 | this.refs[SCROLLVIEW_REF].setNativeProps(props); 150 | }, 151 | 152 | getDefaultProps() { 153 | return { 154 | renderScrollComponent: (props) => 155 | }; 156 | }, 157 | 158 | getInnerViewNode() { 159 | return this.refs[SCROLLVIEW_REF].getInnerViewNode(); 160 | }, 161 | 162 | render() { 163 | return null; 164 | }, 165 | }); 166 | 167 | module.exports = ListView; 168 | -------------------------------------------------------------------------------- /src/components/Navigator.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import createMockComponent from './createMockComponent'; 3 | import View from './View'; 4 | 5 | const NavigatorSceneConfigType = PropTypes.shape({ 6 | gestures: PropTypes.object, 7 | springFriction: PropTypes.number, 8 | springTension: PropTypes.number, 9 | defaultTransitionVelocity: PropTypes.number, 10 | animationInterpolators: React.PropTypes.object, 11 | }); 12 | 13 | const NavigatorSceneConfigs = { 14 | PushFromRight: NavigatorSceneConfigType, 15 | FloatFromRight: NavigatorSceneConfigType, 16 | FloatFromLeft: NavigatorSceneConfigType, 17 | FloatFromBottom: NavigatorSceneConfigType, 18 | FloatFromBottomAndroid: NavigatorSceneConfigType, 19 | FadeAndroid: NavigatorSceneConfigType, 20 | HorizontalSwipeJump: NavigatorSceneConfigType, 21 | HorizontalSwipeJumpFromRight: NavigatorSceneConfigType, 22 | VerticalUpSwipeJump: NavigatorSceneConfigType, 23 | VerticalDownSwipeJump: NavigatorSceneConfigType 24 | }; 25 | 26 | const Navigator = React.createClass({ 27 | propTypes: { 28 | /** 29 | * Optional function that allows configuration about scene animations and 30 | * gestures. Will be invoked with the route and the routeStack and should 31 | * return a scene configuration object 32 | * 33 | * ``` 34 | * (route, routeStack) => Navigator.SceneConfigs.FloatFromRight 35 | * ``` 36 | */ 37 | configureScene: PropTypes.func, 38 | 39 | /** 40 | * Required function which renders the scene for a given route. Will be 41 | * invoked with the route and the navigator object 42 | * 43 | * ``` 44 | * (route, navigator) => 45 | * 46 | * ``` 47 | */ 48 | renderScene: PropTypes.func.isRequired, 49 | 50 | /** 51 | * Specify a route to start on. A route is an object that the navigator 52 | * will use to identify each scene to render. `initialRoute` must be 53 | * a route in the `initialRouteStack` if both props are provided. The 54 | * `initialRoute` will default to the last item in the `initialRouteStack`. 55 | */ 56 | initialRoute: PropTypes.object, 57 | 58 | /** 59 | * Provide a set of routes to initially mount. Required if no initialRoute 60 | * is provided. Otherwise, it will default to an array containing only the 61 | * `initialRoute` 62 | */ 63 | initialRouteStack: PropTypes.arrayOf(PropTypes.object), 64 | 65 | /** 66 | * Will emit the target route upon mounting and before each nav transition 67 | */ 68 | onWillFocus: PropTypes.func, 69 | 70 | /** 71 | * Will be called with the new route of each scene after the transition is 72 | * complete or after the initial mounting 73 | */ 74 | onDidFocus: PropTypes.func, 75 | 76 | /** 77 | * Optionally provide a navigation bar that persists across scene 78 | * transitions 79 | */ 80 | navigationBar: PropTypes.node, 81 | 82 | /** 83 | * Optionally provide the navigator object from a parent Navigator 84 | */ 85 | navigator: PropTypes.object, 86 | 87 | /** 88 | * Styles to apply to the container of each scene 89 | */ 90 | sceneStyle: View.propTypes.style, 91 | }, 92 | 93 | statics: { 94 | BreadcrumbNavigationBar: createMockComponent('NavigatorBreadcrumbNavigationBar'), 95 | NavigationBar: createMockComponent('NavigatorNavigationBar'), 96 | SceneConfigs: NavigatorSceneConfigs, 97 | }, 98 | render() { 99 | return null; 100 | } 101 | }); 102 | 103 | module.exports = Navigator; 104 | -------------------------------------------------------------------------------- /src/components/Picker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import createMockComponent from './createMockComponent'; 3 | 4 | const Picker = React.createClass({ 5 | propTypes: { 6 | children: React.PropTypes.node 7 | }, 8 | statics: { 9 | Item: createMockComponent('Picker.Item') 10 | }, 11 | render() { 12 | return null; 13 | } 14 | }); 15 | 16 | module.exports = Picker; 17 | -------------------------------------------------------------------------------- /src/components/ScrollView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; 3 | import PointPropType from '../propTypes/PointPropType'; 4 | import ScrollResponder from '../mixins/ScrollResponder'; 5 | import View from './View'; 6 | import ViewStylePropTypes from '../propTypes/ViewStylePropTypes'; 7 | import ScrollViewManager from '../NativeModules/ScrollViewManager'; 8 | import styleSheetPropType from '../propTypes/StyleSheetPropType'; 9 | 10 | const { PropTypes } = React; 11 | 12 | const SCROLLVIEW = 'ScrollView'; 13 | const INNERVIEW = 'InnerScrollView'; 14 | 15 | const ScrollView = React.createClass({ 16 | propTypes: { 17 | ...View.propTypes, 18 | /** 19 | * Controls whether iOS should automatically adjust the content inset 20 | * for scroll views that are placed behind a navigation bar or 21 | * tab bar/ toolbar. The default value is true. 22 | * @platform ios 23 | */ 24 | automaticallyAdjustContentInsets: PropTypes.bool, 25 | /** 26 | * The amount by which the scroll view content is inset from the edges 27 | * of the scroll view. Defaults to `{0, 0, 0, 0}`. 28 | * @platform ios 29 | */ 30 | contentInset: EdgeInsetsPropType, 31 | /** 32 | * Used to manually set the starting scroll offset. 33 | * The default value is `{x: 0, y: 0}`. 34 | * @platform ios 35 | */ 36 | contentOffset: PointPropType, 37 | /** 38 | * When true, the scroll view bounces when it reaches the end of the 39 | * content if the content is larger then the scroll view along the axis of 40 | * the scroll direction. When false, it disables all bouncing even if 41 | * the `alwaysBounce*` props are true. The default value is true. 42 | * @platform ios 43 | */ 44 | bounces: PropTypes.bool, 45 | /** 46 | * When true, gestures can drive zoom past min/max and the zoom will animate 47 | * to the min/max value at gesture end, otherwise the zoom will not exceed 48 | * the limits. 49 | * @platform ios 50 | */ 51 | bouncesZoom: PropTypes.bool, 52 | /** 53 | * When true, the scroll view bounces horizontally when it reaches the end 54 | * even if the content is smaller than the scroll view itself. The default 55 | * value is true when `horizontal={true}` and false otherwise. 56 | * @platform ios 57 | */ 58 | alwaysBounceHorizontal: PropTypes.bool, 59 | /** 60 | * When true, the scroll view bounces vertically when it reaches the end 61 | * even if the content is smaller than the scroll view itself. The default 62 | * value is false when `horizontal={true}` and true otherwise. 63 | * @platform ios 64 | */ 65 | alwaysBounceVertical: PropTypes.bool, 66 | /** 67 | * When true, the scroll view automatically centers the content when the 68 | * content is smaller than the scroll view bounds; when the content is 69 | * larger than the scroll view, this property has no effect. The default 70 | * value is false. 71 | * @platform ios 72 | */ 73 | centerContent: PropTypes.bool, 74 | /** 75 | * These styles will be applied to the scroll view content container which 76 | * wraps all of the child views. Example: 77 | * 78 | * return ( 79 | * 80 | * 81 | * ); 82 | * ... 83 | * var styles = StyleSheet.create({ 84 | * contentContainer: { 85 | * paddingVertical: 20 86 | * } 87 | * }); 88 | */ 89 | contentContainerStyle: styleSheetPropType(ViewStylePropTypes), 90 | /** 91 | * A floating-point number that determines how quickly the scroll view 92 | * decelerates after the user lifts their finger. You may also use string 93 | * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings 94 | * for `UIScrollViewDecelerationRateNormal` and 95 | * `UIScrollViewDecelerationRateFast` respectively. 96 | * - Normal: 0.998 (the default) 97 | * - Fast: 0.9 98 | * @platform ios 99 | */ 100 | decelerationRate: PropTypes.oneOfType([ 101 | PropTypes.oneOf(['fast', 'normal']), 102 | PropTypes.number, 103 | ]), 104 | /** 105 | * When true, the scroll view's children are arranged horizontally in a row 106 | * instead of vertically in a column. The default value is false. 107 | */ 108 | horizontal: PropTypes.bool, 109 | /** 110 | * The style of the scroll indicators. 111 | * - `default` (the default), same as `black`. 112 | * - `black`, scroll indicator is black. 113 | * - `white`, scroll indicator is white. 114 | * @platform ios 115 | */ 116 | indicatorStyle: PropTypes.oneOf([ 117 | 'default', // default 118 | 'black', 119 | 'white', 120 | ]), 121 | /** 122 | * When true, the ScrollView will try to lock to only vertical or horizontal 123 | * scrolling while dragging. The default value is false. 124 | * @platform ios 125 | */ 126 | directionalLockEnabled: PropTypes.bool, 127 | /** 128 | * When false, once tracking starts, won't try to drag if the touch moves. 129 | * The default value is true. 130 | * @platform ios 131 | */ 132 | canCancelContentTouches: PropTypes.bool, 133 | /** 134 | * Determines whether the keyboard gets dismissed in response to a drag. 135 | * - 'none' (the default), drags do not dismiss the keyboard. 136 | * - 'on-drag', the keyboard is dismissed when a drag begins. 137 | * - 'interactive', the keyboard is dismissed interactively with the drag and moves in 138 | * synchrony with the touch; dragging upwards cancels the dismissal. 139 | * On android this is not supported and it will have the same behavior as 'none'. 140 | */ 141 | keyboardDismissMode: PropTypes.oneOf([ 142 | 'none', // default 143 | 'interactive', 144 | 'on-drag', 145 | ]), 146 | /** 147 | * Determines when the keyboard should stay visible after a tap. 148 | * 149 | * - 'never' (the default), tapping outside of the focused text input when the keyboard 150 | * is up dismisses the keyboard. When this happens, children won't receive the tap. 151 | * - 'always', the keyboard will not dismiss automatically, and the scroll view will not 152 | * catch taps, but children of the scroll view can catch taps. 153 | * - 'handled', the keyboard will not dismiss automatically when the tap was handled by 154 | * a children, (or captured by an ancestor). 155 | * - false, deprecated, use 'never' instead 156 | * - true, deprecated, use 'always' instead 157 | */ 158 | keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled', false, true]), 159 | /** 160 | * The maximum allowed zoom scale. The default value is 1.0. 161 | * @platform ios 162 | */ 163 | maximumZoomScale: PropTypes.number, 164 | /** 165 | * The minimum allowed zoom scale. The default value is 1.0. 166 | * @platform ios 167 | */ 168 | minimumZoomScale: PropTypes.number, 169 | /** 170 | * Fires at most once per frame during scrolling. The frequency of the 171 | * events can be controlled using the `scrollEventThrottle` prop. 172 | */ 173 | onScroll: PropTypes.func, 174 | /** 175 | * Called when a scrolling animation ends. 176 | * @platform ios 177 | */ 178 | onScrollAnimationEnd: PropTypes.func, 179 | /** 180 | * Called when scrollable content view of the ScrollView changes. It's 181 | * implemented using onLayout handler attached to the content container 182 | * which this ScrollView renders. 183 | */ 184 | onContentSizeChange: PropTypes.func, 185 | /** 186 | * When true, the scroll view stops on multiples of the scroll view's size 187 | * when scrolling. This can be used for horizontal pagination. The default 188 | * value is false. 189 | * @platform ios 190 | */ 191 | pagingEnabled: PropTypes.bool, 192 | /** 193 | * When false, the content does not scroll. 194 | * The default value is true. 195 | * @platform ios 196 | */ 197 | scrollEnabled: PropTypes.bool, 198 | /** 199 | * This controls how often the scroll event will be fired while scrolling 200 | * (in events per seconds). A higher number yields better accuracy for code 201 | * that is tracking the scroll position, but can lead to scroll performance 202 | * problems due to the volume of information being send over the bridge. 203 | * The default value is zero, which means the scroll event will be sent 204 | * only once each time the view is scrolled. 205 | * @platform ios 206 | */ 207 | scrollEventThrottle: PropTypes.number, 208 | /** 209 | * The amount by which the scroll view indicators are inset from the edges 210 | * of the scroll view. This should normally be set to the same value as 211 | * the `contentInset`. Defaults to `{0, 0, 0, 0}`. 212 | * @platform ios 213 | */ 214 | scrollIndicatorInsets: EdgeInsetsPropType, 215 | /** 216 | * When true, the scroll view scrolls to top when the status bar is tapped. 217 | * The default value is true. 218 | * @platform ios 219 | */ 220 | scrollsToTop: PropTypes.bool, 221 | /** 222 | * When true, momentum events will be sent from Android 223 | * This is internal and set automatically by the framework if you have 224 | * onMomentumScrollBegin or onMomentumScrollEnd set on your ScrollView 225 | * @platform android 226 | */ 227 | sendMomentumEvents: PropTypes.bool, 228 | /** 229 | * When true, shows a horizontal scroll indicator. 230 | */ 231 | showsHorizontalScrollIndicator: PropTypes.bool, 232 | /** 233 | * When true, shows a vertical scroll indicator. 234 | */ 235 | showsVerticalScrollIndicator: PropTypes.bool, 236 | /** 237 | * An array of child indices determining which children get docked to the 238 | * top of the screen when scrolling. For example, passing 239 | * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the 240 | * top of the scroll view. This property is not supported in conjunction 241 | * with `horizontal={true}`. 242 | * @platform ios 243 | */ 244 | stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), 245 | style: styleSheetPropType(ViewStylePropTypes), 246 | /** 247 | * When set, causes the scroll view to stop at multiples of the value of 248 | * `snapToInterval`. This can be used for paginating through children 249 | * that have lengths smaller than the scroll view. Used in combination 250 | * with `snapToAlignment`. 251 | * @platform ios 252 | */ 253 | snapToInterval: PropTypes.number, 254 | /** 255 | * When `snapToInterval` is set, `snapToAlignment` will define the relationship 256 | * of the the snapping to the scroll view. 257 | * - `start` (the default) will align the snap at the left (horizontal) or top (vertical) 258 | * - `center` will align the snap in the center 259 | * - `end` will align the snap at the right (horizontal) or bottom (vertical) 260 | * @platform ios 261 | */ 262 | snapToAlignment: PropTypes.oneOf([ 263 | 'start', // default 264 | 'center', 265 | 'end', 266 | ]), 267 | /** 268 | * Experimental: When true, offscreen child views (whose `overflow` value is 269 | * `hidden`) are removed from their native backing superview when offscreen. 270 | * This can improve scrolling performance on long lists. The default value is 271 | * true. 272 | */ 273 | removeClippedSubviews: PropTypes.bool, 274 | /** 275 | * The current scale of the scroll view content. The default value is 1.0. 276 | * @platform ios 277 | */ 278 | zoomScale: PropTypes.number, 279 | 280 | /** 281 | * A RefreshControl component, used to provide pull-to-refresh 282 | * functionality for the ScrollView. 283 | * 284 | * See [RefreshControl](http://facebook.github.io/react-native/docs/refreshcontrol.html). 285 | */ 286 | refreshControl: PropTypes.element, 287 | }, 288 | 289 | mixins: [ScrollResponder.Mixin], 290 | 291 | setNativeProps(props) { 292 | this.refs[SCROLLVIEW].setNativeProps(props); 293 | }, 294 | 295 | /** 296 | * Returns a reference to the underlying scroll responder, which supports 297 | * operations like `scrollTo`. All ScrollView-like components should 298 | * implement this method so that they can be composed while providing access 299 | * to the underlying scroll responder's methods. 300 | */ 301 | getScrollResponder() { 302 | return this; 303 | }, 304 | 305 | getInnerViewNode() { 306 | return React.findNodeHandle(this.refs[INNERVIEW]); 307 | }, 308 | 309 | endRefreshin() { 310 | ScrollViewManager.endRefreshing( 311 | React.findNodeHandle(this) 312 | ); 313 | }, 314 | 315 | scrollTo(destY = 0, destX = 0, animated = true) { 316 | 317 | }, 318 | 319 | render() { 320 | return null; 321 | }, 322 | }); 323 | 324 | module.exports = ScrollView; 325 | -------------------------------------------------------------------------------- /src/components/StatusBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/StatusBar/StatusBar.js 3 | */ 4 | import React from 'react'; 5 | import ColorPropType from '../propTypes/ColorPropType'; 6 | 7 | 8 | let _backgroundColor = ''; 9 | let _barStyle = {}; 10 | let _hidden = false; 11 | let _networkActivityIndicatorVisible = false; 12 | let _translucent = false; 13 | 14 | const StatusBar = React.createClass({ 15 | propTypes: { 16 | animated: React.PropTypes.bool, 17 | barStyle: React.PropTypes.oneOf(['default', 'light-content']), 18 | backgroundColor: ColorPropType, 19 | hidden: React.PropTypes.bool, 20 | networkActivityIndicatorVisible: React.PropTypes.bool, 21 | showHideTransition: React.PropTypes.oneOf(['fade', 'slide']), 22 | translucent: React.PropTypes.bool 23 | }, 24 | 25 | statics: { 26 | setBackgroundColor(backgroundColor, animated) { 27 | _backgroundColor = backgroundColor; 28 | }, 29 | 30 | setBarStyle(barStyle, animated) { 31 | _barStyle = barStyle; 32 | }, 33 | 34 | setHidden(hidden, animated) { 35 | _hidden = hidden; 36 | }, 37 | 38 | setNetworkActivityIndicatorVisible(visible) { 39 | _networkActivityIndicatorVisible = visible; 40 | }, 41 | 42 | setTranslucent(translucent) { 43 | _translucent = translucent; 44 | }, 45 | 46 | __getBackgroundColor() { 47 | return _backgroundColor; 48 | }, 49 | 50 | __getBarStyle() { 51 | return _barStyle; 52 | }, 53 | 54 | __getHidden() { 55 | return _hidden; 56 | }, 57 | 58 | __getNetworkActivityIndicatorVisible() { 59 | return _networkActivityIndicatorVisible; 60 | }, 61 | 62 | __getTranslucent() { 63 | return _translucent; 64 | } 65 | }, 66 | 67 | render() { 68 | return null; 69 | } 70 | }); 71 | 72 | module.exports = StatusBar; 73 | -------------------------------------------------------------------------------- /src/components/TabBarIOS.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import createMockComponent from './createMockComponent'; 3 | 4 | const TabBarIOS = React.createClass({ 5 | propTypes: { 6 | children: React.PropTypes.node 7 | }, 8 | statics: { 9 | Item: createMockComponent('TabBarIOS.Item') 10 | }, 11 | render() { 12 | return null; 13 | } 14 | }); 15 | 16 | module.exports = TabBarIOS; 17 | -------------------------------------------------------------------------------- /src/components/Text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Text/Text.js 3 | */ 4 | import React from 'react'; 5 | import styleSheetPropType from '../propTypes/StyleSheetPropType'; 6 | import TextStylePropTypes from '../propTypes/TextStylePropTypes'; 7 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 8 | 9 | const stylePropType = styleSheetPropType(TextStylePropTypes); 10 | 11 | const Text = React.createClass({ 12 | propTypes: { 13 | /** 14 | * Used to truncate the text with an ellipsis after computing the text 15 | * layout, including line wrapping, such that the total number of lines 16 | * does not exceed this number. 17 | */ 18 | numberOfLines: React.PropTypes.number, 19 | /** 20 | * Invoked on mount and layout changes with 21 | * 22 | * `{nativeEvent: {layout: {x, y, width, height}}}` 23 | */ 24 | onLayout: React.PropTypes.func, 25 | /** 26 | * This function is called on press. 27 | */ 28 | onPress: React.PropTypes.func, 29 | /** 30 | * When true, no visual change is made when text is pressed down. By 31 | * default, a gray oval highlights the text on press down. 32 | * @platform ios 33 | */ 34 | suppressHighlighting: React.PropTypes.bool, 35 | style: stylePropType, 36 | /** 37 | * Used to locate this view in end-to-end tests. 38 | */ 39 | testID: React.PropTypes.string, 40 | /** 41 | * Specifies should fonts scale to respect Text Size accessibility setting on iOS. 42 | * @platform ios 43 | */ 44 | allowFontScaling: React.PropTypes.bool, 45 | }, 46 | mixins: [NativeMethodsMixin], 47 | 48 | render() { 49 | return null; 50 | }, 51 | }); 52 | 53 | module.exports = Text; 54 | -------------------------------------------------------------------------------- /src/components/TextInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextInputState from '../api/TextInputState'; 3 | import TimerMixin from 'react-timer-mixin'; 4 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 5 | import View from './View'; 6 | import Text from './Text'; 7 | 8 | const { PropTypes } = React; 9 | 10 | const TextInput = React.createClass({ 11 | propTypes: { 12 | ...View.propTypes, 13 | /** 14 | * Can tell TextInput to automatically capitalize certain characters. 15 | * 16 | * - characters: all characters, 17 | * - words: first letter of each word 18 | * - sentences: first letter of each sentence (default) 19 | * - none: don't auto capitalize anything 20 | */ 21 | autoCapitalize: PropTypes.oneOf([ 22 | 'none', 23 | 'sentences', 24 | 'words', 25 | 'characters', 26 | ]), 27 | /** 28 | * If false, disables auto-correct. The default value is true. 29 | */ 30 | autoCorrect: PropTypes.bool, 31 | /** 32 | * If true, focuses the input on componentDidMount. 33 | * The default value is false. 34 | */ 35 | autoFocus: PropTypes.bool, 36 | /** 37 | * If false, text is not editable. The default value is true. 38 | */ 39 | editable: PropTypes.bool, 40 | /** 41 | * Determines which keyboard to open, e.g.`numeric`. 42 | * 43 | * The following values work across platforms: 44 | * - default 45 | * - numeric 46 | * - email-address 47 | */ 48 | keyboardType: PropTypes.oneOf([ 49 | // Cross-platform 50 | 'default', 51 | 'email-address', 52 | 'numeric', 53 | 'phone-pad', 54 | // iOS-only 55 | 'ascii-capable', 56 | 'numbers-and-punctuation', 57 | 'url', 58 | 'number-pad', 59 | 'name-phone-pad', 60 | 'decimal-pad', 61 | 'twitter', 62 | 'web-search', 63 | ]), 64 | /** 65 | * Determines the color of the keyboard. 66 | * @platform ios 67 | */ 68 | keyboardAppearance: PropTypes.oneOf([ 69 | 'default', 70 | 'light', 71 | 'dark', 72 | ]), 73 | /** 74 | * Determines how the return key should look. 75 | * @platform ios 76 | */ 77 | returnKeyType: PropTypes.oneOf([ 78 | 'default', 79 | 'go', 80 | 'google', 81 | 'join', 82 | 'next', 83 | 'route', 84 | 'search', 85 | 'send', 86 | 'yahoo', 87 | 'done', 88 | 'emergency-call', 89 | ]), 90 | /** 91 | * Limits the maximum number of characters that can be entered. Use this 92 | * instead of implementing the logic in JS to avoid flicker. 93 | */ 94 | maxLength: PropTypes.number, 95 | /** 96 | * Sets the number of lines for a TextInput. Use it with multiline set to 97 | * true to be able to fill the lines. 98 | * @platform android 99 | */ 100 | numberOfLines: PropTypes.number, 101 | /** 102 | * If true, the keyboard disables the return key when there is no text and 103 | * automatically enables it when there is text. The default value is false. 104 | * @platform ios 105 | */ 106 | enablesReturnKeyAutomatically: PropTypes.bool, 107 | /** 108 | * If true, the text input can be multiple lines. 109 | * The default value is false. 110 | */ 111 | multiline: PropTypes.bool, 112 | /** 113 | * Callback that is called when the text input is blurred 114 | */ 115 | onBlur: PropTypes.func, 116 | /** 117 | * Callback that is called when the text input is focused 118 | */ 119 | onFocus: PropTypes.func, 120 | /** 121 | * Callback that is called when the text input's text changes. 122 | */ 123 | onChange: PropTypes.func, 124 | /** 125 | * Callback that is called when the text input's text changes. 126 | * Changed text is passed as an argument to the callback handler. 127 | */ 128 | onChangeText: PropTypes.func, 129 | /** 130 | * Callback that is called when text input ends. 131 | */ 132 | onEndEditing: PropTypes.func, 133 | /** 134 | * Callback that is called when the text input selection is changed 135 | */ 136 | onSelectionChange: PropTypes.func, 137 | /** 138 | * Callback that is called when the text input's submit button is pressed. 139 | * Invalid if multiline={true} is specified. 140 | */ 141 | onSubmitEditing: PropTypes.func, 142 | /** 143 | * Callback that is called when a key is pressed. 144 | * Pressed key value is passed as an argument to the callback handler. 145 | * Fires before onChange callbacks. 146 | * @platform ios 147 | */ 148 | onKeyPress: PropTypes.func, 149 | /** 150 | * Invoked on mount and layout changes with `{x, y, width, height}`. 151 | */ 152 | onLayout: PropTypes.func, 153 | /** 154 | * The string that will be rendered before text input has been entered 155 | */ 156 | placeholder: PropTypes.string, 157 | /** 158 | * The text color of the placeholder string 159 | */ 160 | placeholderTextColor: PropTypes.string, 161 | /** 162 | * If true, the text input obscures the text entered so that sensitive text 163 | * like passwords stay secure. The default value is false. 164 | */ 165 | secureTextEntry: PropTypes.bool, 166 | /** 167 | * See DocumentSelectionState.js, some state that is responsible for 168 | * maintaining selection information for a document 169 | * @platform ios 170 | */ 171 | // TODO(lmr): requireLibrary 172 | // selectionState: PropTypes.instanceOf(DocumentSelectionState), 173 | /** 174 | * The value to show for the text input. TextInput is a controlled 175 | * component, which means the native value will be forced to match this 176 | * value prop if provided. For most uses this works great, but in some 177 | * cases this may cause flickering - one common cause is preventing edits 178 | * by keeping value the same. In addition to simply setting the same value, 179 | * either set `editable={false}`, or set/update `maxLength` to prevent 180 | * unwanted edits without flicker. 181 | */ 182 | value: PropTypes.string, 183 | /** 184 | * Provides an initial value that will change when the user starts typing. 185 | * Useful for simple use-cases where you don't want to deal with listening 186 | * to events and updating the value prop to keep the controlled state in sync. 187 | */ 188 | defaultValue: PropTypes.string, 189 | /** 190 | * When the clear button should appear on the right side of the text view 191 | * @platform ios 192 | */ 193 | clearButtonMode: PropTypes.oneOf([ 194 | 'never', 195 | 'while-editing', 196 | 'unless-editing', 197 | 'always', 198 | ]), 199 | /** 200 | * If true, clears the text field automatically when editing begins 201 | * @platform ios 202 | */ 203 | clearTextOnFocus: PropTypes.bool, 204 | /** 205 | * If true, all text will automatically be selected on focus 206 | * @platform ios 207 | */ 208 | selectTextOnFocus: PropTypes.bool, 209 | /** 210 | * If true, the text field will blur when submitted. 211 | * The default value is true for single-line fields and false for 212 | * multiline fields. Note that for multiline fields, setting blurOnSubmit 213 | * to true means that pressing return will blur the field and trigger the 214 | * onSubmitEditing event instead of inserting a newline into the field. 215 | * @platform ios 216 | */ 217 | blurOnSubmit: PropTypes.bool, 218 | /** 219 | * Styles 220 | */ 221 | style: Text.propTypes.style, 222 | /** 223 | * Used to locate this view in end-to-end tests 224 | */ 225 | testID: PropTypes.string, 226 | /** 227 | * The color of the textInput underline. 228 | * @platform android 229 | */ 230 | underlineColorAndroid: PropTypes.string, 231 | }, 232 | mixins: [NativeMethodsMixin, TimerMixin], 233 | statics: { 234 | State: TextInputState, 235 | }, 236 | isFocused() { 237 | // TODO(lmr): React.findNodeHandle 238 | return TextInputState.currentlyFocusedField() === 239 | React.findNodeHandle(this.refs.input); 240 | }, 241 | clear() { 242 | 243 | }, 244 | render() { 245 | return null; 246 | }, 247 | }); 248 | 249 | module.exports = TextInput; 250 | -------------------------------------------------------------------------------- /src/components/TouchableNativeFeedback.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | import TouchableWithoutFeedback from './TouchableWithoutFeedback'; 5 | 6 | const TouchableNativeFeedback = React.createClass({ 7 | propTypes: { 8 | ...TouchableWithoutFeedback.propTypes, 9 | 10 | background: React.PropTypes.object 11 | }, 12 | statics: { 13 | SelectableBackground() {}, 14 | SelectableBackgroundBorderless() {}, 15 | Ripple(color, borderless) {} 16 | }, 17 | render() { 18 | return null; 19 | } 20 | }); 21 | 22 | module.exports = TouchableNativeFeedback; 23 | -------------------------------------------------------------------------------- /src/components/TouchableOpacity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/Touchable/TouchableOpacity.js 3 | */ 4 | import React from 'react'; 5 | 6 | import TouchableWithoutFeedback from './TouchableWithoutFeedback'; 7 | 8 | const TouchableOpacity = React.createClass({ 9 | propTypes: { 10 | ...TouchableWithoutFeedback.propTypes, 11 | 12 | /** 13 | * Determines what the opacity of the wrapped view should be when touch is 14 | * active. Defaults to 0.2. 15 | */ 16 | activeOpacity: React.PropTypes.number, 17 | }, 18 | 19 | render() { 20 | return null; 21 | }, 22 | }); 23 | 24 | module.exports = TouchableOpacity; 25 | -------------------------------------------------------------------------------- /src/components/TouchableWithoutFeedback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/Touchable/TouchableWithoutFeedback.js 3 | */ 4 | import React from 'react'; 5 | import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; 6 | import View from './View'; 7 | 8 | const TouchableWithoutFeedback = React.createClass({ 9 | propTypes: { 10 | accessible: React.PropTypes.bool, 11 | accessibilityComponentType: React.PropTypes.oneOf(View.AccessibilityComponentType), 12 | accessibilityTraits: React.PropTypes.oneOfType([ 13 | React.PropTypes.oneOf(View.AccessibilityTraits), 14 | React.PropTypes.arrayOf(React.PropTypes.oneOf(View.AccessibilityTraits)), 15 | ]), 16 | /** 17 | * If true, disable all interactions for this component. 18 | */ 19 | disabled: React.PropTypes.bool, 20 | /** 21 | * Called when the touch is released, but not if cancelled (e.g. by a scroll 22 | * that steals the responder lock). 23 | */ 24 | onPress: React.PropTypes.func, 25 | onPressIn: React.PropTypes.func, 26 | onPressOut: React.PropTypes.func, 27 | /** 28 | * Invoked on mount and layout changes with 29 | * 30 | * `{nativeEvent: {layout: {x, y, width, height}}}` 31 | */ 32 | onLayout: React.PropTypes.func, 33 | 34 | onLongPress: React.PropTypes.func, 35 | 36 | /** 37 | * Delay in ms, from the start of the touch, before onPressIn is called. 38 | */ 39 | delayPressIn: React.PropTypes.number, 40 | /** 41 | * Delay in ms, from the release of the touch, before onPressOut is called. 42 | */ 43 | delayPressOut: React.PropTypes.number, 44 | /** 45 | * Delay in ms, from onPressIn, before onLongPress is called. 46 | */ 47 | delayLongPress: React.PropTypes.number, 48 | /** 49 | * When the scroll view is disabled, this defines how far your touch may 50 | * move off of the button, before deactivating the button. Once deactivated, 51 | * try moving it back and you'll see that the button is once again 52 | * reactivated! Move it back and forth several times while the scroll view 53 | * is disabled. Ensure you pass in a constant to reduce memory allocations. 54 | */ 55 | pressRetentionOffset: EdgeInsetsPropType, 56 | /** 57 | * This defines how far your touch can start away from the button. This is 58 | * added to `pressRetentionOffset` when moving off of the button. 59 | * ** NOTE ** 60 | * The touch area never extends past the parent view bounds and the Z-index 61 | * of sibling views always takes precedence if a touch hits two overlapping 62 | * views. 63 | */ 64 | hitSlop: EdgeInsetsPropType, 65 | }, 66 | render() { 67 | return null; 68 | }, 69 | }); 70 | 71 | module.exports = TouchableWithoutFeedback; 72 | -------------------------------------------------------------------------------- /src/components/View.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/View/View.js 3 | */ 4 | import React from 'react'; 5 | import ViewAccessibility from './ViewAccessibility'; 6 | import NativeMethodsMixin from '../mixins/NativeMethodsMixin'; 7 | import ViewPropTypes from '../propTypes/ViewPropTypes'; 8 | 9 | const { AccessibilityTraits, AccessibilityComponentTypes } = ViewAccessibility; 10 | 11 | const forceTouchAvailable = false; 12 | 13 | const statics = { 14 | AccessibilityComponentType: AccessibilityComponentTypes, 15 | AccessibilityTraits, 16 | /** 17 | * Is 3D Touch / Force Touch available (i.e. will touch events include `force`) 18 | * @platform ios 19 | */ 20 | forceTouchAvailable, 21 | }; 22 | 23 | const View = React.createClass({ 24 | propTypes: ViewPropTypes, 25 | 26 | mixins: [NativeMethodsMixin], 27 | 28 | statics: { 29 | ...statics, 30 | }, 31 | 32 | render() { 33 | return null; 34 | }, 35 | }); 36 | 37 | module.exports = View; 38 | -------------------------------------------------------------------------------- /src/components/ViewAccessibility.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | AccessibilityTraits: [ 3 | 'none', 4 | 'button', 5 | 'link', 6 | 'header', 7 | 'search', 8 | 'image', 9 | 'selected', 10 | 'plays', 11 | 'key', 12 | 'text', 13 | 'summary', 14 | 'disabled', 15 | 'frequentUpdates', 16 | 'startsMedia', 17 | 'adjustable', 18 | 'allowsDirectInteraction', 19 | 'pageTurn', 20 | ], 21 | AccessibilityComponentTypes: [ 22 | 'none', 23 | 'button', 24 | 'radiobutton_checked', 25 | 'radiobutton_unchecked', 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/WebView.js: -------------------------------------------------------------------------------- 1 | import EdgeInsetsPropType from '../propTypes/EdgeInsetsPropType'; 2 | import React from 'react'; 3 | import View from './View'; 4 | import ScrollView from './ScrollView'; 5 | import WebViewManager from '../NativeModules/WebViewManager'; 6 | 7 | const { PropTypes } = React; 8 | 9 | const RCT_WEBVIEW_REF = 'webview'; 10 | 11 | const NavigationType = { 12 | click: WebViewManager.NavigationType.LinkClicked, 13 | formsubmit: WebViewManager.NavigationType.FormSubmitted, 14 | backforward: WebViewManager.NavigationType.BackForward, 15 | reload: WebViewManager.NavigationType.Reload, 16 | formresubmit: WebViewManager.NavigationType.FormResubmitted, 17 | other: WebViewManager.NavigationType.Other, 18 | }; 19 | 20 | const JSNavigationScheme = WebViewManager.JSNavigationScheme; 21 | 22 | const WebView = React.createClass({ 23 | propTypes: { 24 | ...View.propTypes, 25 | url: PropTypes.string, 26 | html: PropTypes.string, 27 | /** 28 | * Function that returns a view to show if there's an error. 29 | */ 30 | renderError: PropTypes.func, // view to show if there's an error 31 | /** 32 | * Function that returns a loading indicator. 33 | */ 34 | renderLoading: PropTypes.func, 35 | /** 36 | * Invoked when load finish 37 | */ 38 | onLoad: PropTypes.func, 39 | /** 40 | * Invoked when load either succeeds or fails 41 | */ 42 | onLoadEnd: PropTypes.func, 43 | /** 44 | * Invoked on load start 45 | */ 46 | onLoadStart: PropTypes.func, 47 | /** 48 | * Invoked when load fails 49 | */ 50 | onError: PropTypes.func, 51 | /** 52 | * @platform ios 53 | */ 54 | bounces: PropTypes.bool, 55 | /** 56 | * A floating-point number that determines how quickly the scroll view 57 | * decelerates after the user lifts their finger. You may also use string 58 | * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings 59 | * for `UIScrollViewDecelerationRateNormal` and 60 | * `UIScrollViewDecelerationRateFast` respectively. 61 | * - Normal: 0.998 62 | * - Fast: 0.9 (the default for iOS WebView) 63 | * @platform ios 64 | */ 65 | decelerationRate: ScrollView.propTypes.decelerationRate, 66 | /** 67 | * @platform ios 68 | */ 69 | scrollEnabled: PropTypes.bool, 70 | automaticallyAdjustContentInsets: PropTypes.bool, 71 | contentInset: EdgeInsetsPropType, 72 | onNavigationStateChange: PropTypes.func, 73 | startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load 74 | style: View.propTypes.style, 75 | 76 | /** 77 | * Used on Android only, JS is enabled by default for WebView on iOS 78 | * @platform android 79 | */ 80 | javaScriptEnabled: PropTypes.bool, 81 | 82 | /** 83 | * Used on Android only, controls whether DOM Storage is enabled or not 84 | * @platform android 85 | */ 86 | domStorageEnabled: PropTypes.bool, 87 | 88 | /** 89 | * Sets the JS to be injected when the webpage loads. 90 | */ 91 | injectedJavaScript: PropTypes.string, 92 | 93 | /** 94 | * Sets whether the webpage scales to fit the view and the user can change the scale. 95 | * @platform ios 96 | */ 97 | scalesPageToFit: PropTypes.bool, 98 | 99 | /** 100 | * Allows custom handling of any webview requests by a JS handler. Return true 101 | * or false from this method to continue loading the request. 102 | * @platform ios 103 | */ 104 | onShouldStartLoadWithRequest: PropTypes.func, 105 | 106 | /** 107 | * Determines whether HTML5 videos play inline or use the native full-screen 108 | * controller. 109 | * default value `false` 110 | * **NOTE** : "In order for video to play inline, not only does this 111 | * property need to be set to true, but the video element in the HTML 112 | * document must also include the webkit-playsinline attribute." 113 | * @platform ios 114 | */ 115 | allowsInlineMediaPlayback: PropTypes.bool, 116 | }, 117 | 118 | statics: { 119 | JSNavigationScheme, 120 | NavigationType, 121 | }, 122 | 123 | getWebViewHandle() { 124 | // TODO(lmr): React.findNodeHandle 125 | return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); 126 | }, 127 | 128 | reload() { 129 | // do nothing 130 | }, 131 | 132 | goForward() { 133 | // do nothing 134 | }, 135 | 136 | goBack() { 137 | // do nothing 138 | }, 139 | 140 | render() { 141 | return null; 142 | }, 143 | }); 144 | 145 | module.exports = WebView; 146 | -------------------------------------------------------------------------------- /src/components/createMockComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function createMockComponent(displayName) { 4 | return React.createClass({ 5 | displayName, 6 | render() { 7 | return null; 8 | }, 9 | }); 10 | } 11 | 12 | module.exports = createMockComponent; 13 | -------------------------------------------------------------------------------- /src/defineGlobalProperty.js: -------------------------------------------------------------------------------- 1 | function defineGlobalProperty(name, value) { 2 | Object.defineProperty(global, name, { 3 | configurable: true, 4 | value: value(), 5 | }); 6 | } 7 | 8 | export default defineGlobalProperty; 9 | -------------------------------------------------------------------------------- /src/mixins/NativeMethodsMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js 3 | */ 4 | module.exports = { 5 | measure(callback) { 6 | 7 | }, 8 | measureLayout(relativeToNativeNode, onSuccess, onFail) { 9 | 10 | }, 11 | setNativeProps(nativeProps) { 12 | 13 | }, 14 | focus() { 15 | 16 | }, 17 | blur() { 18 | 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/mixins/Subscribable.js: -------------------------------------------------------------------------------- 1 | 2 | const SubscribableMixin = { 3 | 4 | componentWillMount() { 5 | this._subscribableSubscriptions = []; 6 | }, 7 | 8 | componentWillUnmount() { 9 | this._subscribableSubscriptions.forEach( 10 | (subscription) => subscription.remove() 11 | ); 12 | this._subscribableSubscriptions = null; 13 | }, 14 | 15 | /** 16 | * Special form of calling `addListener` that *guarantees* that a 17 | * subscription *must* be tied to a component instance, and therefore will 18 | * be cleaned up when the component is unmounted. It is impossible to create 19 | * the subscription and pass it in - this method must be the one to create 20 | * the subscription and therefore can guarantee it is retained in a way that 21 | * will be cleaned up. 22 | * 23 | * @param {EventEmitter} eventEmitter emitter to subscribe to. 24 | * @param {string} eventType Type of event to listen to. 25 | * @param {function} listener Function to invoke when event occurs. 26 | * @param {object} context Object to use as listener context. 27 | */ 28 | addListenerOn(eventEmitter, eventType, listener, context) { 29 | this._subscribableSubscriptions.push( 30 | eventEmitter.addListener(eventType, listener, context) 31 | ); 32 | } 33 | }; 34 | 35 | const Subscribable = { 36 | Mixin: SubscribableMixin, 37 | }; 38 | 39 | module.exports = Subscribable; 40 | -------------------------------------------------------------------------------- /src/plugins/DeviceEventEmitter.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | 3 | const DeviceEventEmitter = new EventEmitter(); 4 | 5 | module.exports = DeviceEventEmitter; 6 | -------------------------------------------------------------------------------- /src/plugins/NativeAppEventEmitter.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | 3 | const NativeAppEventEmitter = new EventEmitter(); 4 | 5 | module.exports = NativeAppEventEmitter; 6 | -------------------------------------------------------------------------------- /src/plugins/Platform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Utilities/Platform.android.js 3 | */ 4 | const Platform = { 5 | OS: 'ios', 6 | Version: undefined, 7 | 8 | /** 9 | * Exposed in react-native-mock for testing purposes. Not part of real API. 10 | */ 11 | __setOS(os) { 12 | Platform.OS = os; 13 | }, 14 | 15 | select(objs) { 16 | return objs[Platform.OS]; 17 | }, 18 | 19 | /** 20 | * Exposed in react-native-mock for testing purposes. Not part of real API. 21 | */ 22 | __setVersion(version) { 23 | Platform.Version = version; 24 | }, 25 | }; 26 | 27 | module.exports = Platform; 28 | -------------------------------------------------------------------------------- /src/plugins/processColor.js: -------------------------------------------------------------------------------- 1 | function processColor() { 2 | // TODO(lmr): seems like a good example of something we should pull directly from RN. 3 | return 0; 4 | } 5 | 6 | module.exports = processColor; 7 | -------------------------------------------------------------------------------- /src/plugins/requireNativeComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/requireNativeComponent.js 3 | */ 4 | import React from 'react'; 5 | 6 | function requireNativeComponent(viewName, componentInterface, extraConfig) { 7 | return React.createClass({ 8 | displayName: viewName, 9 | render() { 10 | return null; 11 | }, 12 | }); 13 | } 14 | 15 | module.exports = requireNativeComponent; 16 | -------------------------------------------------------------------------------- /src/propTypes/ColorPropType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/ColorPropType.js 3 | */ 4 | const ColorPropType = function (props, propName) { 5 | const color = props[propName]; 6 | if (color === undefined || color === null) { 7 | // return; 8 | } 9 | 10 | if (typeof color === 'number') { 11 | // Developers should not use a number, but we are using the prop type 12 | // both for user provided colors and for transformed ones. This isn't ideal 13 | // and should be fixed but will do for now... 14 | // return; 15 | } 16 | 17 | // TODO(lmr): test color 18 | }; 19 | 20 | module.exports = ColorPropType; 21 | -------------------------------------------------------------------------------- /src/propTypes/EdgeInsetsPropType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/EdgeInsetsPropType.js 3 | */ 4 | import React from 'react'; 5 | 6 | const { PropTypes } = React; 7 | 8 | const EdgeInsetsPropType = PropTypes.shape({ 9 | top: PropTypes.number, 10 | left: PropTypes.number, 11 | bottom: PropTypes.number, 12 | right: PropTypes.number, 13 | }); 14 | 15 | module.exports = EdgeInsetsPropType; 16 | -------------------------------------------------------------------------------- /src/propTypes/ImageResizeMode.js: -------------------------------------------------------------------------------- 1 | import Platfrom from '../plugins/Platform'; 2 | 3 | const resizePropTypes = [ 4 | 'contain', 5 | 'cover', 6 | 'stretch', 7 | ]; 8 | 9 | if (Platfrom.OS === 'ios') { 10 | resizePropTypes.push('repeat', 'center'); 11 | } 12 | 13 | module.exports = resizePropTypes; 14 | -------------------------------------------------------------------------------- /src/propTypes/ImageStylePropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Image/ImageStylePropTypes.js 3 | */ 4 | import React from 'react'; 5 | import ColorPropType from './ColorPropType'; 6 | import TransformPropTypes from './TransformPropTypes'; 7 | import ShadowPropTypesIOS from './ShadowPropTypesIOS'; 8 | import LayoutPropTypes from './LayoutPropTypes'; 9 | import ImageResizeMode from './ImageResizeMode'; 10 | 11 | const { PropTypes } = React; 12 | 13 | const ImageStylePropTypes = { 14 | ...LayoutPropTypes, 15 | ...ShadowPropTypesIOS, 16 | ...TransformPropTypes, 17 | resizeMode: PropTypes.oneOf(ImageResizeMode), 18 | backfaceVisibility: PropTypes.oneOf(['visible', 'hidden']), 19 | backgroundColor: ColorPropType, 20 | borderColor: ColorPropType, 21 | borderWidth: PropTypes.number, 22 | borderRadius: PropTypes.number, 23 | overflow: PropTypes.oneOf(['visible', 'hidden']), 24 | 25 | /** 26 | * iOS-Specific style to "tint" an image. 27 | * Changes the color of all the non-transparent pixels to the tintColor. 28 | * @platform ios 29 | */ 30 | tintColor: ColorPropType, 31 | opacity: PropTypes.number, 32 | /** 33 | * When the image has rounded corners, specifying an overlayColor will 34 | * cause the remaining space in the corners to be filled with a solid color. 35 | * This is useful in cases which are not supported by the Android 36 | * implementation of rounded corners: 37 | * - Certain resize modes, such as 'contain' 38 | * - Animated GIFs 39 | * 40 | * A typical way to use this prop is with images displayed on a solid 41 | * background and setting the `overlayColor` to the same color 42 | * as the background. 43 | * 44 | * For details of how this works under the hood, see 45 | * http://frescolib.org/docs/rounded-corners-and-circles.html 46 | * 47 | * @platform android 48 | */ 49 | overlayColor: PropTypes.string, 50 | }; 51 | 52 | module.exports = ImageStylePropTypes; 53 | -------------------------------------------------------------------------------- /src/propTypes/LayoutPropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/LayoutPropTypes.js 3 | */ 4 | import React from 'react'; 5 | 6 | const { PropTypes } = React; 7 | 8 | /** 9 | * React Native's layout system is based on Flexbox and is powered both 10 | * on iOS and Android by an open source project called css-layout: 11 | * https://github.com/facebook/css-layout 12 | * 13 | * The implementation in css-layout is slightly different from what the 14 | * Flexbox spec defines - for example, we chose more sensible default 15 | * values. Please refer to the css-layout README for details. 16 | * 17 | * These properties are a subset of our styles that are consumed by the layout 18 | * algorithm and affect the positioning and sizing of views. 19 | */ 20 | const LayoutPropTypes = { 21 | width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 22 | height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 23 | top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 24 | left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 25 | right: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 26 | bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 27 | margin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 28 | marginVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 29 | marginHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 30 | marginTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 31 | marginBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 32 | marginLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 33 | marginRight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 34 | padding: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 35 | paddingVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 36 | paddingHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 37 | paddingTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 38 | paddingBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 39 | paddingLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 40 | paddingRight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 41 | borderWidth: PropTypes.number, 42 | borderTopWidth: PropTypes.number, 43 | borderRightWidth: PropTypes.number, 44 | borderBottomWidth: PropTypes.number, 45 | borderLeftWidth: PropTypes.number, 46 | 47 | position: PropTypes.oneOf([ 48 | 'absolute', 49 | 'relative' 50 | ]), 51 | 52 | // https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction 53 | flexDirection: PropTypes.oneOf([ 54 | 'row', 55 | 'row-reverse', 56 | 'column', 57 | 'column-reverse' 58 | ]), 59 | 60 | // https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap 61 | flexWrap: PropTypes.oneOf([ 62 | 'wrap', 63 | 'nowrap' 64 | ]), 65 | 66 | // How to align children in the main direction 67 | // https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content 68 | justifyContent: PropTypes.oneOf([ 69 | 'flex-start', 70 | 'flex-end', 71 | 'center', 72 | 'space-between', 73 | 'space-around' 74 | ]), 75 | 76 | // How to align children in the cross direction 77 | // https://developer.mozilla.org/en-US/docs/Web/CSS/align-items 78 | alignItems: PropTypes.oneOf([ 79 | 'flex-start', 80 | 'flex-end', 81 | 'center', 82 | 'stretch' 83 | ]), 84 | 85 | // How to align the element in the cross direction 86 | // https://developer.mozilla.org/en-US/docs/Web/CSS/align-items 87 | alignSelf: PropTypes.oneOf([ 88 | 'auto', 89 | 'flex-start', 90 | 'flex-end', 91 | 'center', 92 | 'stretch' 93 | ]), 94 | 95 | // https://developer.mozilla.org/en-US/docs/Web/CSS/flex 96 | flex: PropTypes.number, 97 | }; 98 | 99 | module.exports = LayoutPropTypes; 100 | -------------------------------------------------------------------------------- /src/propTypes/PointPropType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/PointPropType.js 3 | */ 4 | import React from 'react'; 5 | 6 | const { PropTypes } = React; 7 | 8 | const PointPropType = PropTypes.shape({ 9 | x: PropTypes.number, 10 | y: PropTypes.number, 11 | }); 12 | 13 | module.exports = PointPropType; 14 | -------------------------------------------------------------------------------- /src/propTypes/ShadowPropTypesIOS.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ColorPropType from './ColorPropType'; 3 | 4 | const { PropTypes } = React; 5 | 6 | const ShadowPropTypesIOS = { 7 | /** 8 | * Sets the drop shadow color 9 | * @platform ios 10 | */ 11 | shadowColor: ColorPropType, 12 | /** 13 | * Sets the drop shadow offset 14 | * @platform ios 15 | */ 16 | shadowOffset: PropTypes.shape({ 17 | width: PropTypes.number, 18 | height: PropTypes.number, 19 | }), 20 | /** 21 | * Sets the drop shadow opacity (multiplied by the color's alpha component) 22 | * @platform ios 23 | */ 24 | shadowOpacity: PropTypes.number, 25 | /** 26 | * Sets the drop shadow blur radius 27 | * @platform ios 28 | */ 29 | shadowRadius: PropTypes.number, 30 | }; 31 | 32 | module.exports = ShadowPropTypesIOS; 33 | -------------------------------------------------------------------------------- /src/propTypes/StyleSheetPropType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheetPropType.js 3 | */ 4 | import React from 'react'; 5 | import flattenStyle from './flattenStyle'; 6 | 7 | const { PropTypes } = React; 8 | 9 | function StyleSheetPropType(shape) { 10 | const shapePropType = PropTypes.shape(shape); 11 | return function (props, propName, componentName, ...rest) { 12 | let newProps = props; 13 | if (props[propName]) { 14 | // Just make a dummy prop object with only the flattened style 15 | newProps = {}; 16 | newProps[propName] = flattenStyle(props[propName]); 17 | } 18 | return shapePropType(newProps, propName, componentName, ...rest); 19 | }; 20 | } 21 | 22 | module.exports = StyleSheetPropType; 23 | -------------------------------------------------------------------------------- /src/propTypes/TextStylePropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Text/TextStylePropTypes.js 3 | */ 4 | import React from 'react'; 5 | import ColorPropType from './ColorPropType'; 6 | import ViewStylePropTypes from './ViewStylePropTypes'; 7 | 8 | const { PropTypes } = React; 9 | 10 | // TODO: use spread instead of Object.assign/create after #6560135 is fixed 11 | const TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { 12 | color: ColorPropType, 13 | fontFamily: PropTypes.string, 14 | fontSize: PropTypes.number, 15 | fontStyle: PropTypes.oneOf(['normal', 'italic']), 16 | /** 17 | * Specifies font weight. The values 'normal' and 'bold' are supported for 18 | * most fonts. Not all fonts have a variant for each of the numeric values, 19 | * in that case the closest one is chosen. 20 | */ 21 | fontWeight: PropTypes.oneOf( 22 | ['normal', 'bold', 23 | '100', '200', '300', '400', '500', '600', '700', '800', '900'] 24 | ), 25 | textShadowOffset: PropTypes.shape( 26 | { 27 | width: PropTypes.number, 28 | height: PropTypes.number 29 | } 30 | ), 31 | textShadowRadius: PropTypes.number, 32 | textShadowColor: ColorPropType, 33 | /** 34 | * @platform ios 35 | */ 36 | letterSpacing: PropTypes.number, 37 | lineHeight: PropTypes.number, 38 | /** 39 | * Specifies text alignment. The value 'justify' is only supported on iOS. 40 | */ 41 | textAlign: PropTypes.oneOf( 42 | ['auto', 'left', 'right', 'center', 'justify'] 43 | ), 44 | /** 45 | * @platform android 46 | */ 47 | textAlignVertical: PropTypes.oneOf( 48 | ['auto', 'top', 'bottom', 'center'] 49 | ), 50 | /** 51 | * @platform ios 52 | */ 53 | textDecorationLine: PropTypes.oneOf( 54 | ['none', 'underline', 'line-through', 'underline line-through'] 55 | ), 56 | /** 57 | * @platform ios 58 | */ 59 | textDecorationStyle: PropTypes.oneOf( 60 | ['solid', 'double', 'dotted', 'dashed'] 61 | ), 62 | /** 63 | * @platform ios 64 | */ 65 | textDecorationColor: ColorPropType, 66 | /** 67 | * @platform ios 68 | */ 69 | writingDirection: PropTypes.oneOf( 70 | ['auto', 'ltr', 'rtl'] 71 | ), 72 | }); 73 | 74 | module.exports = TextStylePropTypes; 75 | -------------------------------------------------------------------------------- /src/propTypes/TransformPropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/TransformPropTypes.js 3 | */ 4 | import React from 'react'; 5 | 6 | const { PropTypes } = React; 7 | 8 | const arrayOfNumberPropType = PropTypes.arrayOf(PropTypes.number); 9 | 10 | const transformMatrixPropType = function (props, propName, componentName, ...rest) { 11 | if (props.transform && props.transformMatrix) { 12 | return new Error( 13 | 'transformMatrix and transform styles cannot be used on the same ' + 14 | 'component' 15 | ); 16 | } 17 | return arrayOfNumberPropType(props, propName, componentName, ...rest); 18 | }; 19 | 20 | const transformPropTypes = { 21 | transform: PropTypes.arrayOf( 22 | PropTypes.oneOfType([ 23 | PropTypes.shape({ perspective: PropTypes.number }), 24 | PropTypes.shape({ rotate: PropTypes.string }), 25 | PropTypes.shape({ rotateX: PropTypes.string }), 26 | PropTypes.shape({ rotateY: PropTypes.string }), 27 | PropTypes.shape({ rotateZ: PropTypes.string }), 28 | PropTypes.shape({ scale: PropTypes.number }), 29 | PropTypes.shape({ scaleX: PropTypes.number }), 30 | PropTypes.shape({ scaleY: PropTypes.number }), 31 | PropTypes.shape({ translateX: PropTypes.number }), 32 | PropTypes.shape({ translateY: PropTypes.number }), 33 | PropTypes.shape({ skewX: PropTypes.string }), 34 | PropTypes.shape({ skewY: PropTypes.string }), 35 | ]) 36 | ), 37 | transformMatrix: transformMatrixPropType, 38 | }; 39 | 40 | module.exports = transformPropTypes; 41 | -------------------------------------------------------------------------------- /src/propTypes/ViewStylePropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/Components/View/ViewStylePropTypes.js 3 | */ 4 | import React from 'react'; 5 | import ColorPropType from './ColorPropType'; 6 | import LayoutPropTypes from './LayoutPropTypes'; 7 | import ShadowPropTypesIOS from './ShadowPropTypesIOS'; 8 | import TransformPropTypes from './TransformPropTypes'; 9 | 10 | const { PropTypes } = React; 11 | 12 | /** 13 | * Warning: Some of these properties may not be supported in all releases. 14 | */ 15 | const ViewStylePropTypes = { 16 | ...LayoutPropTypes, 17 | ...ShadowPropTypesIOS, 18 | ...TransformPropTypes, 19 | backfaceVisibility: PropTypes.oneOf(['visible', 'hidden']), 20 | backgroundColor: ColorPropType, 21 | borderColor: ColorPropType, 22 | borderTopColor: ColorPropType, 23 | borderRightColor: ColorPropType, 24 | borderBottomColor: ColorPropType, 25 | borderLeftColor: ColorPropType, 26 | borderRadius: PropTypes.number, 27 | borderTopLeftRadius: PropTypes.number, 28 | borderTopRightRadius: PropTypes.number, 29 | borderBottomLeftRadius: PropTypes.number, 30 | borderBottomRightRadius: PropTypes.number, 31 | borderStyle: PropTypes.oneOf(['solid', 'dotted', 'dashed']), 32 | borderWidth: PropTypes.number, 33 | borderTopWidth: PropTypes.number, 34 | borderRightWidth: PropTypes.number, 35 | borderBottomWidth: PropTypes.number, 36 | borderLeftWidth: PropTypes.number, 37 | opacity: PropTypes.number, 38 | overflow: PropTypes.oneOf(['visible', 'hidden']), 39 | /** 40 | * (Android-only) Sets the elevation of a view, using Android's underlying 41 | * [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation). 42 | * This adds a drop shadow to the item and affects z-order for overlapping views. 43 | * Only supported on Android 5.0+, has no effect on earlier versions. 44 | * @platform android 45 | */ 46 | elevation: PropTypes.number, 47 | }; 48 | 49 | module.exports = ViewStylePropTypes; 50 | -------------------------------------------------------------------------------- /src/propTypes/flattenStyle.js: -------------------------------------------------------------------------------- 1 | function flattenStyle(style) { 2 | if (!style) { 3 | return undefined; 4 | } 5 | if (!Array.isArray(style)) { 6 | return style; 7 | } 8 | return Object.assign({}, ...style); 9 | } 10 | 11 | module.exports = flattenStyle; 12 | -------------------------------------------------------------------------------- /src/react-native.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/facebook/react-native/blob/master/Libraries/react-native/react-native.js 3 | */ 4 | import React from 'react'; 5 | 6 | import createMockComponent from './components/createMockComponent'; 7 | import defineGlobalProperty from './defineGlobalProperty'; 8 | 9 | // Export React, plus some native additions. 10 | const ReactNative = { 11 | // Components 12 | ActivityIndicator: require('./components/ActivityIndicator'), 13 | ActivityIndicatorIOS: require('./components/ActivityIndicatorIOS'), 14 | ART: require('./components/ART'), 15 | Button: createMockComponent('Button'), 16 | DatePickerIOS: createMockComponent('DatePickerIOS'), 17 | DrawerLayoutAndroid: require('./components/DrawerLayoutAndroid'), 18 | Image: require('./components/Image'), 19 | ImageEditor: createMockComponent('ImageEditor'), 20 | ImageStore: createMockComponent('ImageStore'), 21 | KeyboardAvoidingView: createMockComponent('KeyboardAvoidingView'), 22 | ListView: require('./components/ListView'), 23 | MapView: createMockComponent('MapView'), 24 | Modal: createMockComponent('Modal'), 25 | Navigator: require('./components/Navigator'), 26 | NavigatorIOS: createMockComponent('NavigatorIOS'), 27 | Picker: require('./components/Picker'), 28 | PickerIOS: createMockComponent('PickerIOS'), 29 | ProgressBarAndroid: createMockComponent('ProgressBarAndroid'), 30 | ProgressViewIOS: createMockComponent('ProgressViewIOS'), 31 | ScrollView: require('./components/ScrollView'), 32 | SegmentedControlIOS: createMockComponent('SegmentedControlIOS'), 33 | SliderIOS: createMockComponent('SliderIOS'), 34 | SnapshotViewIOS: createMockComponent('SnapshotViewIOS'), 35 | Switch: createMockComponent('Switch'), 36 | PullToRefreshViewAndroid: createMockComponent('PullToRefreshViewAndroid'), 37 | RecyclerViewBackedScrollView: createMockComponent('RecyclerViewBackedScrollView'), 38 | RefreshControl: createMockComponent('RefreshControl'), 39 | StatusBar: require('./components/StatusBar'), 40 | SwitchAndroid: createMockComponent('SwitchAndroid'), 41 | SwitchIOS: createMockComponent('SwitchIOS'), 42 | TabBarIOS: require('./components/TabBarIOS'), 43 | Text: require('./components/Text'), 44 | TextInput: require('./components/TextInput'), 45 | ToastAndroid: createMockComponent('ToastAndroid'), 46 | ToolbarAndroid: createMockComponent('ToolbarAndroid'), 47 | Touchable: createMockComponent('Touchable'), 48 | TouchableHighlight: createMockComponent('TouchableHighlight'), 49 | TouchableNativeFeedback: require('./components/TouchableNativeFeedback'), 50 | TouchableOpacity: require('./components/TouchableOpacity'), 51 | TouchableWithoutFeedback: require('./components/TouchableWithoutFeedback'), 52 | View: require('./components/View'), 53 | ViewPagerAndroid: createMockComponent('ViewPagerAndroid'), 54 | WebView: require('./components/WebView'), 55 | 56 | // APIs 57 | ActionSheetIOS: require('./api/ActionSheetIOS'), 58 | Alert: require('./api/Alert'), 59 | AlertIOS: require('./api/AlertIOS'), 60 | Animated: require('./api/Animated'), 61 | AppRegistry: require('./api/AppRegistry'), 62 | AppState: require('./api/AppState'), 63 | AppStateIOS: require('./api/AppStateIOS'), 64 | AsyncStorage: require('./api/AsyncStorage'), 65 | BackAndroid: require('./api/BackAndroid'), 66 | CameraRoll: require('./api/CameraRoll'), 67 | Clipboard: require('./NativeModules/Clipboard'), 68 | DatePickerAndroid: require('./api/DatePickerAndroid'), 69 | Dimensions: require('./api/Dimensions'), 70 | Easing: require('./api/Animated/Easing'), 71 | ImagePickerIOS: require('./api/ImagePickerIOS'), 72 | IntentAndroid: require('./api/IntentAndroid'), 73 | InteractionManager: require('./api/InteractionManager'), 74 | Keyboard: require('./api/Keyboard'), 75 | LayoutAnimation: require('./api/LayoutAnimation'), 76 | Linking: require('./api/Linking'), 77 | LinkingIOS: require('./api/LinkingIOS'), 78 | NetInfo: require('./api/NetInfo'), 79 | PanResponder: require('./api/PanResponder'), 80 | PixelRatio: require('./api/PixelRatio'), 81 | PushNotificationIOS: require('./api/PushNotificationIOS'), 82 | Settings: require('./api/Settings'), 83 | Share: require('./api/Share'), 84 | StatusBarIOS: require('./api/StatusBarIOS'), 85 | StyleSheet: require('./api/StyleSheet'), 86 | TimePickerAndroid: require('./api/TimePickerAndroid'), 87 | UIManager: require('./NativeModules/UIManager'), 88 | VibrationIOS: require('./api/VibrationIOS'), 89 | 90 | // Plugins 91 | DeviceEventEmitter: require('./plugins/DeviceEventEmitter'), 92 | NativeAppEventEmitter: require('./plugins/NativeAppEventEmitter'), 93 | NativeEventEmitter: require('./Libraries/EventEmitter/NativeEventEmitter'), 94 | NativeModules: require('./NativeModules'), 95 | Platform: require('./plugins/Platform'), 96 | processColor: require('./plugins/processColor'), 97 | requireNativeComponent: require('./plugins/requireNativeComponent'), 98 | 99 | // Prop Types 100 | ColorPropType: require('./propTypes/ColorPropType'), 101 | EdgeInsetsPropType: require('./propTypes/EdgeInsetsPropType'), 102 | PointPropType: require('./propTypes/PointPropType'), 103 | NavigationExperimental: require('./Libraries/NavigationExperimental'), 104 | ViewPropTypes: require('./propTypes/ViewPropTypes'), 105 | }; 106 | 107 | 108 | // See http://facebook.github.io/react/docs/addons.html 109 | const ReactNativeAddons = { 110 | // LinkedStateMixin: require('react-addons-linked-state-mixin') deprecated, 111 | Perf: require('react-addons-perf'), 112 | PureRenderMixin: require('react-addons-pure-render-mixin'), 113 | TestModule: require('./NativeModules/TestModule'), 114 | TestUtils: require('react-addons-test-utils'), 115 | // TODO(lmr): not sure where to find this 116 | // batchedUpdates: require('ReactUpdates').batchedUpdates, deprecated 117 | // cloneWithProps: require('react-addons-clone-with-props'), deprecated 118 | createFragment: require('react-addons-create-fragment'), 119 | update: require('react-addons-update'), 120 | }; 121 | 122 | Object.assign(ReactNative, React, { addons: ReactNativeAddons }); 123 | 124 | // Global properties defined in https://github.com/facebook/react-native/blob/master/Libraries/Core/InitializeCore.js 125 | defineGlobalProperty('XMLHttpRequest', () => require('./Libraries/Network/XMLHttpRequest')); 126 | defineGlobalProperty('FormData', () => require('./Libraries/Network/FormData')); 127 | defineGlobalProperty('Headers', () => require('./Libraries/Network/Headers')); 128 | defineGlobalProperty('Response', () => require('./Libraries/Network/Response')); 129 | 130 | module.exports = ReactNative; 131 | -------------------------------------------------------------------------------- /src/requireLibrary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Playing around with the idea of requiring libraries from RN directly. 3 | * 4 | * Next steps: utilize RN's packager transform in order to parse the code. 5 | */ 6 | const path = require('path'); 7 | const absolutePathToRN = require.resolve('react-native'); 8 | const relativePathToRN = path.relative(__filename, absolutePathToRN); 9 | const pathToLibraries = path.join(relativePathToRN, '../../'); 10 | 11 | 12 | function requireLibrary(lib) { 13 | const relPath = path.join(pathToLibraries, lib); 14 | const absPath = path.resolve(__filename, relPath); 15 | return require(absPath); 16 | } 17 | 18 | // Example Usage: 19 | // var normalizeColor = requireLibrary('StyleSheet/normalizeColor.js'); 20 | module.exports = requireLibrary; 21 | -------------------------------------------------------------------------------- /test/StyleSheet.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from '../src/react-native'; 2 | import { expect } from 'chai'; 3 | 4 | 5 | describe('StyleSheet', () => { 6 | let styles; 7 | 8 | beforeEach(function () { 9 | styles = StyleSheet.create({ 10 | listItem: { 11 | flex: 1, 12 | fontSize: 16, 13 | color: 'white' 14 | }, 15 | selectedListItem: { 16 | color: 'green' 17 | }, 18 | headerItem: { 19 | fontWeight: 'bold' 20 | } 21 | }); 22 | }); 23 | 24 | it('flatten', () => { 25 | const result = StyleSheet.flatten(styles.listItem); 26 | const expectedResult = { 27 | flex: 1, 28 | fontSize: 16, 29 | color: 'white' 30 | }; 31 | expect(result).to.deep.equal(expectedResult); 32 | }); 33 | 34 | it('flatten with array', () => { 35 | const result = StyleSheet.flatten([styles.listItem, styles.selectedListItem]); 36 | const expectedResult = { 37 | flex: 1, 38 | fontSize: 16, 39 | color: 'green' 40 | }; 41 | expect(result).to.deep.equal(expectedResult); 42 | }); 43 | 44 | it('flatten with nested array', () => { 45 | const result = StyleSheet.flatten( 46 | [styles.listItem, [styles.headerItem, styles.selectedListItem]] 47 | ); 48 | const expectedResult = { 49 | flex: 1, 50 | fontSize: 16, 51 | color: 'green', 52 | fontWeight: 'bold' 53 | }; 54 | expect(result).to.deep.equal(expectedResult); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/basic-test.js: -------------------------------------------------------------------------------- 1 | import React from '../src/react-native'; 2 | import { expect } from 'chai'; 3 | 4 | describe('Requires', () => { 5 | it('requires', () => { 6 | console.log(Object.keys(React)); // eslint-disable-line no-console 7 | expect(true).to.equal(true); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/components/DrawerLayoutAndroid.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-expressions: 0 */ 2 | import React from 'react'; 3 | import { DrawerLayoutAndroid } from '../../src/react-native'; 4 | import { expect } from 'chai'; 5 | import ReactTestUtils from 'react-addons-test-utils'; 6 | 7 | describe('DrawerLayoutAndroid', () => { 8 | it('should render an empty DrawerLayoutAndroid', () => { 9 | const renderer = ReactTestUtils.createRenderer(); 10 | const wrapper = renderer.getRenderOutput( {}} />); 11 | expect(wrapper).to.be.null; 12 | }); 13 | 14 | it('should have static properties for the positions', () => { 15 | expect(DrawerLayoutAndroid.positions).to.be.an.object; 16 | expect(DrawerLayoutAndroid.positions).to.have.property('Left'); 17 | expect(DrawerLayoutAndroid.positions).to.have.property('Right'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/components/Picker.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Picker from '../../src/components/Picker.js'; 3 | 4 | describe('Picker', () => { 5 | it('is renderable', () => { 6 | expect(Picker).to.be.a('function'); 7 | }); 8 | 9 | it('.Item is renderable', () => { 10 | expect(Picker.Item).to.be.a('function'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/components/TabBarIOS.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import TabBarIOS from '../../src/components/TabBarIOS.js'; 3 | 4 | describe('TabBarIOS', () => { 5 | it('is renderable', () => { 6 | expect(TabBarIOS).to.be.a('function'); 7 | }); 8 | 9 | it('.Item is renderable', () => { 10 | expect(TabBarIOS.Item).to.be.a('function'); 11 | }); 12 | }); 13 | --------------------------------------------------------------------------------