├── LICENSE ├── README.md ├── package.json └── src ├── CustomComponents.js ├── EmitterSubscription.js ├── EventEmitter.js ├── EventSubscription.js ├── EventSubscriptionVendor.js ├── InteractionMixin.js ├── NavigationContext.js ├── NavigationEvent.js ├── NavigationEventEmitter.js ├── NavigationRouteStack.js ├── NavigationTreeNode.js ├── Navigator.js ├── NavigatorBreadcrumbNavigationBar.js ├── NavigatorBreadcrumbNavigationBarStyles.android.js ├── NavigatorBreadcrumbNavigationBarStyles.ios.js ├── NavigatorNavigationBar.js ├── NavigatorNavigationBarStylesAndroid.js ├── NavigatorNavigationBarStylesIOS.js ├── NavigatorSceneConfigs.js ├── Subscribable.js ├── __tests__ ├── NavigationContext-test.js ├── NavigationEvent-test.js ├── NavigationEventEmitter-test.js ├── NavigationRouteStack-test.js └── NavigationTreeNode-test.js ├── buildStyleInterpolator.js ├── clamp.js ├── flattenStyle.js ├── guid.js ├── merge.js ├── mergeHelpers.js └── mergeInto.js /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT 2 | 3 | For React Native Custom Components software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Facebook, Inc. (“Facebook”) owns all right, title and interest, including all intellectual property and other proprietary rights, in and to the React Native Custom Components software (the “Software”). Subject to your compliance with these terms, you are hereby granted a non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the Software; and (2) reproduce and distribute the Software as part of your own software (“Your Software”). Facebook reserves all rights not expressly granted to you in this license agreement. 8 | 9 | THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Legacy Custom Components 2 | 3 | This is a module for legacy "CustomComponents" to gracefully deprecate. 4 | 5 | ## Navigator 6 | 7 | The navigator component in this module will behave identically as the one in old version of React native, with one exception: 8 | 9 | Latest documentation is available here: http://facebook.github.io/react-native/releases/0.43/docs/navigator.html 10 | 11 | 12 | ### Breaking Changes from react-native 13 | 14 | - Navigator.props.sceneStyle must be a plain object, not a stylesheet! 15 | 16 | (this breaking change is needed to avoid calling React Native's private APIs) 17 | 18 | 19 | # 本插件修改过两个地方 20 | 屏蔽了isMounted,在0.44以上会报警告 21 | 22 | _handleSpringUpdate: function() { 23 | // if (!this.isMounted()) { 24 | // return; 25 | // } 26 | // Prioritize handling transition in progress over a gesture: 27 | 28 | _completeTransition: function() { 29 | // if (!this.isMounted()) { 30 | // return; 31 | // } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-deprecated-custom-components", 3 | "version": "0.1.0", 4 | "description": "Deprecated custom components that originally shipped with React Native", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:facebookarchive/react-native-custom-components.git" 8 | }, 9 | "main": "src/CustomComponents.js", 10 | "dependencies": { 11 | "fbjs": "~0.8.9", 12 | "immutable": "~3.7.6", 13 | "react-timer-mixin": "^0.13.2", 14 | "rebound": "^0.0.13" 15 | }, 16 | "peerDependencies": { 17 | "react-native": "*" 18 | } 19 | } -------------------------------------------------------------------------------- /src/CustomComponents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 27 | 28 | const Navigator = require('./Navigator'); 29 | 30 | module.exports = { 31 | Navigator, 32 | }; 33 | -------------------------------------------------------------------------------- /src/EmitterSubscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @noflow 26 | */ 27 | 'use strict'; 28 | 29 | const EventSubscription = require('./EventSubscription'); 30 | 31 | import type EventEmitter from './EventEmitter'; 32 | import type EventSubscriptionVendor from './EventSubscriptionVendor'; 33 | 34 | /** 35 | * EmitterSubscription represents a subscription with listener and context data. 36 | */ 37 | class EmitterSubscription extends EventSubscription { 38 | 39 | emitter: EventEmitter; 40 | listener: Function; 41 | context: ?Object; 42 | 43 | /** 44 | * @param {EventEmitter} emitter - The event emitter that registered this 45 | * subscription 46 | * @param {EventSubscriptionVendor} subscriber - The subscriber that controls 47 | * this subscription 48 | * @param {function} listener - Function to invoke when the specified event is 49 | * emitted 50 | * @param {*} context - Optional context object to use when invoking the 51 | * listener 52 | */ 53 | constructor( 54 | emitter: EventEmitter, 55 | subscriber: EventSubscriptionVendor, 56 | listener: Function, 57 | context: ?Object 58 | ) { 59 | super(subscriber); 60 | this.emitter = emitter; 61 | this.listener = listener; 62 | this.context = context; 63 | } 64 | 65 | /** 66 | * Removes this subscription from the emitter that registered it. 67 | * Note: we're overriding the `remove()` method of EventSubscription here 68 | * but deliberately not calling `super.remove()` as the responsibility 69 | * for removing the subscription lies with the EventEmitter. 70 | */ 71 | remove() { 72 | this.emitter.removeSubscription(this); 73 | } 74 | } 75 | 76 | module.exports = EmitterSubscription; 77 | -------------------------------------------------------------------------------- /src/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @noflow 26 | * @typecheck 27 | */ 28 | 'use strict'; 29 | 30 | const EmitterSubscription = require('./EmitterSubscription'); 31 | const EventSubscriptionVendor = require('./EventSubscriptionVendor'); 32 | 33 | const emptyFunction = require('fbjs/lib/emptyFunction'); 34 | const invariant = require('fbjs/lib/invariant'); 35 | 36 | /** 37 | * @class EventEmitter 38 | * @description 39 | * An EventEmitter is responsible for managing a set of listeners and publishing 40 | * events to them when it is told that such events happened. In addition to the 41 | * data for the given event it also sends a event control object which allows 42 | * the listeners/handlers to prevent the default behavior of the given event. 43 | * 44 | * The emitter is designed to be generic enough to support all the different 45 | * contexts in which one might want to emit events. It is a simple multicast 46 | * mechanism on top of which extra functionality can be composed. For example, a 47 | * more advanced emitter may use an EventHolder and EventFactory. 48 | */ 49 | class EventEmitter { 50 | 51 | _subscriber: EventSubscriptionVendor; 52 | _currentSubscription: ?EmitterSubscription; 53 | 54 | /** 55 | * @constructor 56 | * 57 | * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance 58 | * to use. If omitted, a new subscriber will be created for the emitter. 59 | */ 60 | constructor(subscriber: ?EventSubscriptionVendor) { 61 | this._subscriber = subscriber || new EventSubscriptionVendor(); 62 | } 63 | 64 | /** 65 | * Adds a listener to be invoked when events of the specified type are 66 | * emitted. An optional calling context may be provided. The data arguments 67 | * emitted will be passed to the listener function. 68 | * 69 | * TODO: Annotate the listener arg's type. This is tricky because listeners 70 | * can be invoked with varargs. 71 | * 72 | * @param {string} eventType - Name of the event to listen to 73 | * @param {function} listener - Function to invoke when the specified event is 74 | * emitted 75 | * @param {*} context - Optional context object to use when invoking the 76 | * listener 77 | */ 78 | addListener( 79 | eventType: string, listener: Function, context: ?Object): EmitterSubscription { 80 | 81 | return (this._subscriber.addSubscription( 82 | eventType, 83 | new EmitterSubscription(this, this._subscriber, listener, context) 84 | ) : any); 85 | } 86 | 87 | /** 88 | * Similar to addListener, except that the listener is removed after it is 89 | * invoked once. 90 | * 91 | * @param {string} eventType - Name of the event to listen to 92 | * @param {function} listener - Function to invoke only once when the 93 | * specified event is emitted 94 | * @param {*} context - Optional context object to use when invoking the 95 | * listener 96 | */ 97 | once(eventType: string, listener: Function, context: ?Object): EmitterSubscription { 98 | return this.addListener(eventType, (...args) => { 99 | this.removeCurrentListener(); 100 | listener.apply(context, args); 101 | }); 102 | } 103 | 104 | /** 105 | * Removes all of the registered listeners, including those registered as 106 | * listener maps. 107 | * 108 | * @param {?string} eventType - Optional name of the event whose registered 109 | * listeners to remove 110 | */ 111 | removeAllListeners(eventType: ?string) { 112 | this._subscriber.removeAllSubscriptions(eventType); 113 | } 114 | 115 | /** 116 | * Provides an API that can be called during an eventing cycle to remove the 117 | * last listener that was invoked. This allows a developer to provide an event 118 | * object that can remove the listener (or listener map) during the 119 | * invocation. 120 | * 121 | * If it is called when not inside of an emitting cycle it will throw. 122 | * 123 | * @throws {Error} When called not during an eventing cycle 124 | * 125 | * @example 126 | * var subscription = emitter.addListenerMap({ 127 | * someEvent: function(data, event) { 128 | * console.log(data); 129 | * emitter.removeCurrentListener(); 130 | * } 131 | * }); 132 | * 133 | * emitter.emit('someEvent', 'abc'); // logs 'abc' 134 | * emitter.emit('someEvent', 'def'); // does not log anything 135 | */ 136 | removeCurrentListener() { 137 | invariant( 138 | !!this._currentSubscription, 139 | 'Not in an emitting cycle; there is no current subscription' 140 | ); 141 | this.removeSubscription(this._currentSubscription); 142 | } 143 | 144 | /** 145 | * Removes a specific subscription. Called by the `remove()` method of the 146 | * subscription itself to ensure any necessary cleanup is performed. 147 | */ 148 | removeSubscription(subscription: EmitterSubscription) { 149 | invariant( 150 | subscription.emitter === this, 151 | 'Subscription does not belong to this emitter.' 152 | ); 153 | this._subscriber.removeSubscription(subscription); 154 | } 155 | 156 | /** 157 | * Returns an array of listeners that are currently registered for the given 158 | * event. 159 | * 160 | * @param {string} eventType - Name of the event to query 161 | * @returns {array} 162 | */ 163 | listeners(eventType: string): [EmitterSubscription] { 164 | const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); 165 | return subscriptions 166 | ? subscriptions.filter(emptyFunction.thatReturnsTrue).map( 167 | function(subscription) { 168 | return subscription.listener; 169 | }) 170 | : []; 171 | } 172 | 173 | /** 174 | * Emits an event of the given type with the given data. All handlers of that 175 | * particular type will be notified. 176 | * 177 | * @param {string} eventType - Name of the event to emit 178 | * @param {...*} Arbitrary arguments to be passed to each registered listener 179 | * 180 | * @example 181 | * emitter.addListener('someEvent', function(message) { 182 | * console.log(message); 183 | * }); 184 | * 185 | * emitter.emit('someEvent', 'abc'); // logs 'abc' 186 | */ 187 | emit(eventType: string) { 188 | const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); 189 | if (subscriptions) { 190 | for (let i = 0, l = subscriptions.length; i < l; i++) { 191 | const subscription = subscriptions[i]; 192 | 193 | // The subscription may have been removed during this event loop. 194 | if (subscription) { 195 | this._currentSubscription = subscription; 196 | subscription.listener.apply( 197 | subscription.context, 198 | Array.prototype.slice.call(arguments, 1) 199 | ); 200 | } 201 | } 202 | this._currentSubscription = null; 203 | } 204 | } 205 | 206 | /** 207 | * Removes the given listener for event of specific type. 208 | * 209 | * @param {string} eventType - Name of the event to emit 210 | * @param {function} listener - Function to invoke when the specified event is 211 | * emitted 212 | * 213 | * @example 214 | * emitter.removeListener('someEvent', function(message) { 215 | * console.log(message); 216 | * }); // removes the listener if already registered 217 | * 218 | */ 219 | removeListener(eventType: String, listener) { 220 | const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); 221 | if (subscriptions) { 222 | for (let i = 0, l = subscriptions.length; i < l; i++) { 223 | const subscription = subscriptions[i]; 224 | 225 | // The subscription may have been removed during this event loop. 226 | // its listener matches the listener in method parameters 227 | if (subscription && subscription.listener === listener) { 228 | subscription.remove(); 229 | } 230 | } 231 | } 232 | } 233 | } 234 | 235 | module.exports = EventEmitter; 236 | -------------------------------------------------------------------------------- /src/EventSubscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | import type EventSubscriptionVendor from './EventSubscriptionVendor'; 30 | 31 | /** 32 | * EventSubscription represents a subscription to a particular event. It can 33 | * remove its own subscription. 34 | */ 35 | class EventSubscription { 36 | 37 | eventType: string; 38 | key: number; 39 | subscriber: EventSubscriptionVendor; 40 | 41 | /** 42 | * @param {EventSubscriptionVendor} subscriber the subscriber that controls 43 | * this subscription. 44 | */ 45 | constructor(subscriber: EventSubscriptionVendor) { 46 | this.subscriber = subscriber; 47 | } 48 | 49 | /** 50 | * Removes this subscription from the subscriber that controls it. 51 | */ 52 | remove() { 53 | this.subscriber.removeSubscription(this); 54 | } 55 | } 56 | 57 | module.exports = EventSubscription; 58 | -------------------------------------------------------------------------------- /src/EventSubscriptionVendor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | const invariant = require('fbjs/lib/invariant'); 30 | 31 | import type EventSubscription from './EventSubscription'; 32 | 33 | /** 34 | * EventSubscriptionVendor stores a set of EventSubscriptions that are 35 | * subscribed to a particular event type. 36 | */ 37 | class EventSubscriptionVendor { 38 | 39 | _subscriptionsForType: Object; 40 | _currentSubscription: ?EventSubscription; 41 | 42 | constructor() { 43 | this._subscriptionsForType = {}; 44 | this._currentSubscription = null; 45 | } 46 | 47 | /** 48 | * Adds a subscription keyed by an event type. 49 | * 50 | * @param {string} eventType 51 | * @param {EventSubscription} subscription 52 | */ 53 | addSubscription( 54 | eventType: string, subscription: EventSubscription): EventSubscription { 55 | invariant( 56 | subscription.subscriber === this, 57 | 'The subscriber of the subscription is incorrectly set.'); 58 | if (!this._subscriptionsForType[eventType]) { 59 | this._subscriptionsForType[eventType] = []; 60 | } 61 | const key = this._subscriptionsForType[eventType].length; 62 | this._subscriptionsForType[eventType].push(subscription); 63 | subscription.eventType = eventType; 64 | subscription.key = key; 65 | return subscription; 66 | } 67 | 68 | /** 69 | * Removes a bulk set of the subscriptions. 70 | * 71 | * @param {?string} eventType - Optional name of the event type whose 72 | * registered supscriptions to remove, if null remove all subscriptions. 73 | */ 74 | removeAllSubscriptions(eventType: ?string) { 75 | if (eventType === undefined) { 76 | this._subscriptionsForType = {}; 77 | } else { 78 | delete this._subscriptionsForType[eventType]; 79 | } 80 | } 81 | 82 | /** 83 | * Removes a specific subscription. Instead of calling this function, call 84 | * `subscription.remove()` directly. 85 | * 86 | * @param {object} subscription 87 | */ 88 | removeSubscription(subscription: Object) { 89 | const eventType = subscription.eventType; 90 | const key = subscription.key; 91 | 92 | const subscriptionsForType = this._subscriptionsForType[eventType]; 93 | if (subscriptionsForType) { 94 | delete subscriptionsForType[key]; 95 | } 96 | } 97 | 98 | /** 99 | * Returns the array of subscriptions that are currently registered for the 100 | * given event type. 101 | * 102 | * Note: This array can be potentially sparse as subscriptions are deleted 103 | * from it when they are removed. 104 | * 105 | * TODO: This returns a nullable array. wat? 106 | * 107 | * @param {string} eventType 108 | * @returns {?array} 109 | */ 110 | getSubscriptionsForType(eventType: string): ?[EventSubscription] { 111 | return this._subscriptionsForType[eventType]; 112 | } 113 | } 114 | 115 | module.exports = EventSubscriptionVendor; 116 | -------------------------------------------------------------------------------- /src/InteractionMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | import {InteractionManager} from 'react-native'; 30 | 31 | /** 32 | * This mixin provides safe versions of InteractionManager start/end methods 33 | * that ensures `clearInteractionHandle` is always called 34 | * once per start, even if the component is unmounted. 35 | */ 36 | var InteractionMixin = { 37 | componentWillUnmount: function() { 38 | while (this._interactionMixinHandles.length) { 39 | InteractionManager.clearInteractionHandle( 40 | this._interactionMixinHandles.pop() 41 | ); 42 | } 43 | }, 44 | 45 | _interactionMixinHandles: ([]: Array), 46 | 47 | createInteractionHandle: function() { 48 | var handle = InteractionManager.createInteractionHandle(); 49 | this._interactionMixinHandles.push(handle); 50 | return handle; 51 | }, 52 | 53 | clearInteractionHandle: function(clearHandle: number) { 54 | InteractionManager.clearInteractionHandle(clearHandle); 55 | this._interactionMixinHandles = this._interactionMixinHandles.filter( 56 | handle => handle !== clearHandle 57 | ); 58 | }, 59 | 60 | /** 61 | * Schedule work for after all interactions have completed. 62 | * 63 | * @param {function} callback 64 | */ 65 | runAfterInteractions: function(callback: Function) { 66 | InteractionManager.runAfterInteractions(callback); 67 | }, 68 | }; 69 | 70 | module.exports = InteractionMixin; 71 | -------------------------------------------------------------------------------- /src/NavigationContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @noflow 26 | */ 27 | 'use strict'; 28 | 29 | var NavigationEvent = require('./NavigationEvent'); 30 | var NavigationEventEmitter = require('./NavigationEventEmitter'); 31 | var NavigationTreeNode = require('./NavigationTreeNode'); 32 | 33 | var emptyFunction = require('fbjs/lib/emptyFunction'); 34 | var invariant = require('fbjs/lib/invariant'); 35 | 36 | import type EventSubscription from 'EventSubscription'; 37 | 38 | var { 39 | AT_TARGET, 40 | BUBBLING_PHASE, 41 | CAPTURING_PHASE, 42 | } = NavigationEvent; 43 | 44 | // Event types that do not support event bubbling, capturing and 45 | // reconciliation API (e.g event.preventDefault(), event.stopPropagation()). 46 | var LegacyEventTypes = new Set([ 47 | 'willfocus', 48 | 'didfocus', 49 | ]); 50 | 51 | /** 52 | * Class that contains the info and methods for app navigation. 53 | */ 54 | class NavigationContext { 55 | __node: NavigationTreeNode; 56 | _bubbleEventEmitter: ?NavigationEventEmitter; 57 | _captureEventEmitter: ?NavigationEventEmitter; 58 | _currentRoute: any; 59 | _emitCounter: number; 60 | _emitQueue: Array; 61 | 62 | constructor() { 63 | this._bubbleEventEmitter = new NavigationEventEmitter(this); 64 | this._captureEventEmitter = new NavigationEventEmitter(this); 65 | this._currentRoute = null; 66 | 67 | // Sets the protected property `__node`. 68 | this.__node = new NavigationTreeNode(this); 69 | 70 | this._emitCounter = 0; 71 | this._emitQueue = []; 72 | 73 | this.addListener('willfocus', this._onFocus); 74 | this.addListener('didfocus', this._onFocus); 75 | } 76 | 77 | /* $FlowFixMe - get/set properties not yet supported */ 78 | get parent(): ?NavigationContext { 79 | var parent = this.__node.getParent(); 80 | return parent ? parent.getValue() : null; 81 | } 82 | 83 | /* $FlowFixMe - get/set properties not yet supported */ 84 | get top(): ?NavigationContext { 85 | var result = null; 86 | var parentNode = this.__node.getParent(); 87 | while (parentNode) { 88 | result = parentNode.getValue(); 89 | parentNode = parentNode.getParent(); 90 | } 91 | return result; 92 | } 93 | 94 | /* $FlowFixMe - get/set properties not yet supported */ 95 | get currentRoute(): any { 96 | return this._currentRoute; 97 | } 98 | 99 | appendChild(childContext: NavigationContext): void { 100 | this.__node.appendChild(childContext.__node); 101 | } 102 | 103 | addListener( 104 | eventType: string, 105 | listener: Function, 106 | useCapture: ?boolean 107 | ): EventSubscription { 108 | if (LegacyEventTypes.has(eventType)) { 109 | useCapture = false; 110 | } 111 | 112 | var emitter = useCapture ? 113 | this._captureEventEmitter : 114 | this._bubbleEventEmitter; 115 | 116 | if (emitter) { 117 | return emitter.addListener(eventType, listener, this); 118 | } else { 119 | return {remove: emptyFunction}; 120 | } 121 | } 122 | 123 | emit(eventType: String, data: any, didEmitCallback: ?Function): void { 124 | if (this._emitCounter > 0) { 125 | // An event cycle that was previously created hasn't finished yet. 126 | // Put this event cycle into the queue and will finish them later. 127 | var args: any = Array.prototype.slice.call(arguments); 128 | this._emitQueue.push(args); 129 | return; 130 | } 131 | 132 | this._emitCounter++; 133 | 134 | if (LegacyEventTypes.has(eventType)) { 135 | // Legacy events does not support event bubbling and reconciliation. 136 | this.__emit( 137 | eventType, 138 | data, 139 | null, 140 | { 141 | defaultPrevented: false, 142 | eventPhase: AT_TARGET, 143 | propagationStopped: true, 144 | target: this, 145 | } 146 | ); 147 | } else { 148 | var targets = [this]; 149 | var parentTarget = this.parent; 150 | while (parentTarget) { 151 | targets.unshift(parentTarget); 152 | parentTarget = parentTarget.parent; 153 | } 154 | 155 | var propagationStopped = false; 156 | var defaultPrevented = false; 157 | var callback = (event) => { 158 | propagationStopped = propagationStopped || event.isPropagationStopped(); 159 | defaultPrevented = defaultPrevented || event.defaultPrevented; 160 | }; 161 | 162 | // Capture phase 163 | targets.some((currentTarget) => { 164 | if (propagationStopped) { 165 | return true; 166 | } 167 | 168 | var extraInfo = { 169 | defaultPrevented, 170 | eventPhase: CAPTURING_PHASE, 171 | propagationStopped, 172 | target: this, 173 | }; 174 | 175 | currentTarget.__emit(eventType, data, callback, extraInfo); 176 | }, this); 177 | 178 | // bubble phase 179 | targets.reverse().some((currentTarget) => { 180 | if (propagationStopped) { 181 | return true; 182 | } 183 | var extraInfo = { 184 | defaultPrevented, 185 | eventPhase: BUBBLING_PHASE, 186 | propagationStopped, 187 | target: this, 188 | }; 189 | currentTarget.__emit(eventType, data, callback, extraInfo); 190 | }, this); 191 | } 192 | 193 | if (didEmitCallback) { 194 | var event = NavigationEvent.pool(eventType, this, data); 195 | propagationStopped && event.stopPropagation(); 196 | defaultPrevented && event.preventDefault(); 197 | didEmitCallback.call(this, event); 198 | event.dispose(); 199 | } 200 | 201 | this._emitCounter--; 202 | while (this._emitQueue.length) { 203 | var args: any = this._emitQueue.shift(); 204 | this.emit.apply(this, args); 205 | } 206 | } 207 | 208 | dispose(): void { 209 | // clean up everything. 210 | this._bubbleEventEmitter && this._bubbleEventEmitter.removeAllListeners(); 211 | this._captureEventEmitter && this._captureEventEmitter.removeAllListeners(); 212 | this._bubbleEventEmitter = null; 213 | this._captureEventEmitter = null; 214 | this._currentRoute = null; 215 | } 216 | 217 | // This method `__method` is protected. 218 | __emit( 219 | eventType: String, 220 | data: any, 221 | didEmitCallback: ?Function, 222 | extraInfo: Object, 223 | ): void { 224 | var emitter; 225 | switch (extraInfo.eventPhase) { 226 | case CAPTURING_PHASE: // phase = 1 227 | emitter = this._captureEventEmitter; 228 | break; 229 | 230 | case AT_TARGET: // phase = 2 231 | emitter = this._bubbleEventEmitter; 232 | break; 233 | 234 | case BUBBLING_PHASE: // phase = 3 235 | emitter = this._bubbleEventEmitter; 236 | break; 237 | 238 | default: 239 | invariant(false, 'invalid event phase %s', extraInfo.eventPhase); 240 | } 241 | 242 | if (extraInfo.target === this) { 243 | // phase = 2 244 | extraInfo.eventPhase = AT_TARGET; 245 | } 246 | 247 | if (emitter) { 248 | emitter.emit( 249 | eventType, 250 | data, 251 | didEmitCallback, 252 | extraInfo 253 | ); 254 | } 255 | } 256 | 257 | _onFocus(event: NavigationEvent): void { 258 | invariant( 259 | event.data && event.data.hasOwnProperty('route'), 260 | 'event type "%s" should provide route', 261 | event.type 262 | ); 263 | 264 | this._currentRoute = event.data.route; 265 | } 266 | } 267 | 268 | module.exports = NavigationContext; 269 | -------------------------------------------------------------------------------- /src/NavigationEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | const invariant = require('fbjs/lib/invariant'); 30 | 31 | class NavigationEventPool { 32 | _list: Array; 33 | 34 | constructor() { 35 | this._list = []; 36 | } 37 | 38 | get(type: string, currentTarget: Object, data: any): NavigationEvent { 39 | let event; 40 | if (this._list.length > 0) { 41 | event = this._list.pop(); 42 | event.constructor.call(event, type, currentTarget, data); 43 | } else { 44 | event = new NavigationEvent(type, currentTarget, data); 45 | } 46 | return event; 47 | } 48 | 49 | put(event: NavigationEvent) { 50 | this._list.push(event); 51 | } 52 | } 53 | 54 | const _navigationEventPool = new NavigationEventPool(); 55 | 56 | /** 57 | * The NavigationEvent interface represents any event of the navigation. 58 | * It contains common properties and methods to any event. 59 | * 60 | * == Important Properties == 61 | * 62 | * - target: 63 | * A reference to the navigation context that dispatched the event. It is 64 | * different from event.currentTarget when the event handler is called during 65 | * the bubbling or capturing phase of the event. 66 | * 67 | * - currentTarget: 68 | * Identifies the current target for the event, as the event traverses the 69 | * navigation context tree. It always refers to the navigation context the 70 | * event handler has been attached to as opposed to event.target which 71 | * identifies the navigation context on which the event occurred. 72 | * 73 | * - eventPhase: 74 | * Returns an integer value which specifies the current evaluation phase of 75 | * the event flow; possible values are listed in NavigationEvent phase 76 | * constants below. 77 | */ 78 | class NavigationEvent { 79 | static AT_TARGET: number; 80 | static BUBBLING_PHASE: number; 81 | static CAPTURING_PHASE: number; 82 | static NONE: number; 83 | 84 | _currentTarget: ?Object; 85 | _data: any; 86 | _defaultPrevented: boolean; 87 | _disposed: boolean; 88 | _propagationStopped: boolean; 89 | _type: string; 90 | 91 | target: ?Object; 92 | 93 | // Returns an integer value which specifies the current evaluation phase of 94 | // the event flow. 95 | eventPhase: number; 96 | 97 | static pool(type: string, currentTarget: Object, data: any): NavigationEvent { 98 | return _navigationEventPool.get(type, currentTarget, data); 99 | } 100 | 101 | constructor(type: string, currentTarget: Object, data: any) { 102 | this.target = currentTarget; 103 | this.eventPhase = NavigationEvent.NONE; 104 | 105 | this._type = type; 106 | this._currentTarget = currentTarget; 107 | this._data = data; 108 | this._defaultPrevented = false; 109 | this._disposed = false; 110 | this._propagationStopped = false; 111 | } 112 | 113 | get type(): string { 114 | return this._type; 115 | } 116 | 117 | get currentTarget(): ?Object { 118 | return this._currentTarget; 119 | } 120 | 121 | get data(): any { 122 | return this._data; 123 | } 124 | 125 | get defaultPrevented(): boolean { 126 | return this._defaultPrevented; 127 | } 128 | 129 | preventDefault(): void { 130 | this._defaultPrevented = true; 131 | } 132 | 133 | stopPropagation(): void { 134 | this._propagationStopped = true; 135 | } 136 | 137 | stop(): void { 138 | this.preventDefault(); 139 | this.stopPropagation(); 140 | } 141 | 142 | isPropagationStopped(): boolean { 143 | return this._propagationStopped; 144 | } 145 | 146 | /** 147 | * Dispose the event. 148 | * NavigationEvent shall be disposed after being emitted by 149 | * `NavigationEventEmitter`. 150 | */ 151 | dispose(): void { 152 | invariant(!this._disposed, 'NavigationEvent is already disposed'); 153 | this._disposed = true; 154 | 155 | // Clean up. 156 | this.target = null; 157 | this.eventPhase = NavigationEvent.NONE; 158 | this._type = ''; 159 | this._currentTarget = null; 160 | this._data = null; 161 | this._defaultPrevented = false; 162 | 163 | // Put this back to the pool to reuse the instance. 164 | _navigationEventPool.put(this); 165 | } 166 | } 167 | 168 | /** 169 | * Event phase constants. 170 | * These values describe which phase the event flow is currently being 171 | * evaluated. 172 | */ 173 | 174 | // No event is being processed at this time. 175 | NavigationEvent.NONE = 0; 176 | 177 | // The event is being propagated through the currentTarget's ancestor objects. 178 | NavigationEvent.CAPTURING_PHASE = 1; 179 | 180 | // The event has arrived at the event's currentTarget. Event listeners registered for 181 | // this phase are called at this time. 182 | NavigationEvent.AT_TARGET = 2; 183 | 184 | // The event is propagating back up through the currentTarget's ancestors in reverse 185 | // order, starting with the parent. This is known as bubbling, and occurs only 186 | // if event propagation isn't prevented. Event listeners registered for this 187 | // phase are triggered during this process. 188 | NavigationEvent.BUBBLING_PHASE = 3; 189 | 190 | module.exports = NavigationEvent; 191 | -------------------------------------------------------------------------------- /src/NavigationEventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | var EventEmitter = require('./EventEmitter'); 30 | var NavigationEvent = require('./NavigationEvent'); 31 | 32 | type ExtraInfo = { 33 | defaultPrevented: ?boolean, 34 | eventPhase: ?number, 35 | propagationStopped: ?boolean, 36 | target: ?Object, 37 | }; 38 | 39 | class NavigationEventEmitter extends EventEmitter { 40 | _emitQueue: Array; 41 | _emitting: boolean; 42 | _target: Object; 43 | 44 | constructor(target: Object) { 45 | super(); 46 | this._emitting = false; 47 | this._emitQueue = []; 48 | this._target = target; 49 | } 50 | 51 | emit( 52 | eventType: string, 53 | data: any, 54 | didEmitCallback: ?Function, 55 | extraInfo: ?ExtraInfo 56 | ): void { 57 | if (this._emitting) { 58 | // An event cycle that was previously created hasn't finished yet. 59 | // Put this event cycle into the queue and will finish them later. 60 | var args: any = Array.prototype.slice.call(arguments); 61 | this._emitQueue.push(args); 62 | return; 63 | } 64 | 65 | this._emitting = true; 66 | 67 | var event = NavigationEvent.pool(eventType, this._target, data); 68 | 69 | if (extraInfo) { 70 | if (extraInfo.target) { 71 | event.target = extraInfo.target; 72 | } 73 | 74 | if (extraInfo.eventPhase) { 75 | event.eventPhase = extraInfo.eventPhase; 76 | } 77 | 78 | if (extraInfo.defaultPrevented) { 79 | event.preventDefault(); 80 | } 81 | 82 | if (extraInfo.propagationStopped) { 83 | event.stopPropagation(); 84 | } 85 | } 86 | 87 | // EventEmitter#emit only takes `eventType` as `String`. Casting `eventType` 88 | // to `String` to make @flow happy. 89 | super.emit(String(eventType), event); 90 | 91 | if (typeof didEmitCallback === 'function') { 92 | didEmitCallback.call(this._target, event); 93 | } 94 | event.dispose(); 95 | 96 | this._emitting = false; 97 | 98 | while (this._emitQueue.length) { 99 | var args: any = this._emitQueue.shift(); 100 | this.emit.apply(this, args); 101 | } 102 | } 103 | } 104 | 105 | module.exports = NavigationEventEmitter; 106 | -------------------------------------------------------------------------------- /src/NavigationRouteStack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | var immutable = require('immutable'); 30 | var invariant = require('fbjs/lib/invariant'); 31 | 32 | type IterationCallback = (route: any, index: number, key: string) => void; 33 | 34 | var {List, Set} = immutable; 35 | 36 | function isRouteEmpty(route: any): boolean { 37 | return (route === undefined || route === null || route === '') || false; 38 | } 39 | 40 | var _nextID = 0; 41 | 42 | class RouteNode { 43 | key: string; 44 | value: any; 45 | constructor(route: any) { 46 | // Key value gets bigger incrementally. Developer can compare the 47 | // keys of two routes then know which route is added to the stack 48 | // earlier. 49 | this.key = String(_nextID++); 50 | 51 | this.value = route; 52 | } 53 | } 54 | 55 | var StackDiffRecord = immutable.Record({ 56 | key: null, 57 | route: null, 58 | index: null, 59 | }); 60 | 61 | /** 62 | * The immutable route stack. 63 | */ 64 | class RouteStack { 65 | _index: number; 66 | 67 | _routeNodes: List; 68 | 69 | constructor(index: number, routeNodes: List) { 70 | invariant( 71 | routeNodes.size > 0, 72 | 'size must not be empty' 73 | ); 74 | 75 | invariant( 76 | index > -1 && index <= routeNodes.size - 1, 77 | 'index out of bound' 78 | ); 79 | 80 | this._routeNodes = routeNodes; 81 | this._index = index; 82 | } 83 | 84 | get size(): number { 85 | return this._routeNodes.size; 86 | } 87 | 88 | get index(): number { 89 | return this._index; 90 | } 91 | 92 | toArray(): Array { 93 | var result = []; 94 | var ii = 0; 95 | var nodes = this._routeNodes; 96 | while (ii < nodes.size) { 97 | result.push(nodes.get(ii).value); 98 | ii++; 99 | } 100 | return result; 101 | } 102 | 103 | get(index: number): any { 104 | if (index < 0 || index > this._routeNodes.size - 1) { 105 | return null; 106 | } 107 | return this._routeNodes.get(index).value; 108 | } 109 | 110 | /** 111 | * Returns the key associated with the route. 112 | * When a route is added to a stack, the stack creates a key for this route. 113 | * The key will persist until the initial stack and its derived stack 114 | * no longer contains this route. 115 | */ 116 | keyOf(route: any): ?string { 117 | if (isRouteEmpty(route)) { 118 | return null; 119 | } 120 | var index = this.indexOf(route); 121 | return index > -1 ? 122 | this._routeNodes.get(index).key : 123 | null; 124 | } 125 | 126 | indexOf(route: any): number { 127 | if (isRouteEmpty(route)) { 128 | return -1; 129 | } 130 | 131 | var finder = (node) => { 132 | return (node: RouteNode).value === route; 133 | }; 134 | 135 | return this._routeNodes.findIndex(finder, this); 136 | } 137 | 138 | slice(begin?: number, end?: number): RouteStack { 139 | var routeNodes = this._routeNodes.slice(begin, end); 140 | var index = Math.min(this._index, routeNodes.size - 1); 141 | return this._update(index, routeNodes); 142 | } 143 | 144 | /** 145 | * Returns a new stack with the provided route appended, 146 | * starting at this stack size. 147 | */ 148 | push(route: any): RouteStack { 149 | 150 | invariant( 151 | !isRouteEmpty(route), 152 | 'Must supply route to push' 153 | ); 154 | 155 | invariant(this._routeNodes.indexOf(route) === -1, 'route must be unique'); 156 | 157 | // When pushing, removes the rest of the routes past the current index. 158 | var routeNodes = this._routeNodes.withMutations((list: List) => { 159 | list.slice(0, this._index + 1).push(new RouteNode(route)); 160 | }); 161 | 162 | return this._update(routeNodes.size - 1, routeNodes); 163 | } 164 | 165 | /** 166 | * Returns a new stack a size ones less than this stack, 167 | * excluding the last index in this stack. 168 | */ 169 | pop(): RouteStack { 170 | invariant(this._routeNodes.size > 1, 'should not pop routeNodes stack to empty'); 171 | 172 | // When popping, removes the rest of the routes past the current index. 173 | var routeNodes = this._routeNodes.slice(0, this._index); 174 | return this._update(routeNodes.size - 1, routeNodes); 175 | } 176 | 177 | jumpToIndex(index: number): RouteStack { 178 | invariant( 179 | index > -1 && index < this._routeNodes.size, 180 | 'index out of bound' 181 | ); 182 | 183 | return this._update(index, this._routeNodes); 184 | } 185 | 186 | /** 187 | * Replace a route in the navigation stack. 188 | * 189 | * `index` specifies the route in the stack that should be replaced. 190 | * If it's negative, it counts from the back. 191 | */ 192 | replaceAtIndex(index: number, route: any): RouteStack { 193 | invariant( 194 | !isRouteEmpty(route), 195 | 'Must supply route to replace' 196 | ); 197 | 198 | if (this.get(index) === route) { 199 | return this; 200 | } 201 | 202 | invariant(this.indexOf(route) === -1, 'route must be unique'); 203 | 204 | if (index < 0) { 205 | index += this._routeNodes.size; 206 | } 207 | 208 | invariant( 209 | index > -1 && index < this._routeNodes.size, 210 | 'index out of bound' 211 | ); 212 | 213 | var routeNodes = this._routeNodes.set(index, new RouteNode(route)); 214 | return this._update(index, routeNodes); 215 | } 216 | 217 | // Iterations 218 | forEach(callback: IterationCallback, context: ?Object): void { 219 | var ii = 0; 220 | var nodes = this._routeNodes; 221 | while (ii < nodes.size) { 222 | var node = nodes.get(ii); 223 | callback.call(context, node.value, ii, node.key); 224 | ii++; 225 | } 226 | } 227 | 228 | mapToArray(callback: IterationCallback, context: ?Object): Array { 229 | var result = []; 230 | this.forEach((route, index, key) => { 231 | result.push(callback.call(context, route, index, key)); 232 | }); 233 | return result; 234 | } 235 | 236 | /** 237 | * Returns a Set excluding any routes contained within the stack given. 238 | */ 239 | subtract(stack: RouteStack): Set { 240 | var items = []; 241 | this._routeNodes.forEach((node: RouteNode, index: number) => { 242 | if (!stack._routeNodes.contains(node)) { 243 | items.push( 244 | new StackDiffRecord({ 245 | route: node.value, 246 | index: index, 247 | key: node.key, 248 | }) 249 | ); 250 | } 251 | }); 252 | return new Set(items); 253 | } 254 | 255 | _update(index: number, routeNodes: List): RouteStack { 256 | if (this._index === index && this._routeNodes === routeNodes) { 257 | return this; 258 | } 259 | return new RouteStack(index, routeNodes); 260 | } 261 | } 262 | 263 | /** 264 | * The first class data structure for NavigationContext to manage the navigation 265 | * stack of routes. 266 | */ 267 | class NavigationRouteStack extends RouteStack { 268 | constructor(index: number, routeNodes: Array) { 269 | // For now, `RouteStack` internally, uses an immutable `List` to keep 270 | // track of routeNodes. Since using `List` is really just the implementation 271 | // detail, we don't want to accept `routeNodes` as `list` from constructor 272 | // for developer. 273 | var nodes = routeNodes.map((route) => { 274 | invariant(!isRouteEmpty(route), 'route must not be mepty'); 275 | return new RouteNode(route); 276 | }); 277 | 278 | super(index, new List(nodes)); 279 | } 280 | } 281 | 282 | module.exports = NavigationRouteStack; 283 | -------------------------------------------------------------------------------- /src/NavigationTreeNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | var invariant = require('fbjs/lib/invariant'); 30 | var immutable = require('immutable'); 31 | 32 | var {List} = immutable; 33 | 34 | /** 35 | * Utility to build a tree of nodes. 36 | * Note that this tree does not perform cyclic redundancy check 37 | * while appending child node. 38 | */ 39 | class NavigationTreeNode { 40 | __parent: ?NavigationTreeNode; 41 | 42 | _children: List; 43 | 44 | _value: any; 45 | 46 | constructor(value: any) { 47 | this.__parent = null; 48 | this._children = new List(); 49 | this._value = value; 50 | } 51 | 52 | getValue(): any { 53 | return this._value; 54 | } 55 | 56 | getParent(): ?NavigationTreeNode { 57 | return this.__parent; 58 | } 59 | 60 | getChildrenCount(): number { 61 | return this._children.size; 62 | } 63 | 64 | getChildAt(index: number): ?NavigationTreeNode { 65 | return index > -1 && index < this._children.size ? 66 | this._children.get(index) : 67 | null; 68 | } 69 | 70 | appendChild(child: NavigationTreeNode): void { 71 | if (child.__parent) { 72 | child.__parent.removeChild(child); 73 | } 74 | child.__parent = this; 75 | this._children = this._children.push(child); 76 | } 77 | 78 | removeChild(child: NavigationTreeNode): void { 79 | var index = this._children.indexOf(child); 80 | 81 | invariant( 82 | index > -1, 83 | 'The node to be removed is not a child of this node.' 84 | ); 85 | 86 | child.__parent = null; 87 | 88 | this._children = this._children.splice(index, 1); 89 | } 90 | 91 | indexOf(child: NavigationTreeNode): number { 92 | return this._children.indexOf(child); 93 | } 94 | 95 | forEach(callback: Function, context: any): void { 96 | this._children.forEach(callback, context); 97 | } 98 | 99 | map(callback: Function, context: any): Array { 100 | return this._children.map(callback, context).toJS(); 101 | } 102 | 103 | some(callback: Function, context: any): boolean { 104 | return this._children.some(callback, context); 105 | } 106 | } 107 | 108 | 109 | module.exports = NavigationTreeNode; 110 | -------------------------------------------------------------------------------- /src/NavigatorBreadcrumbNavigationBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | import { 29 | Platform, 30 | StyleSheet, 31 | View, 32 | } from 'react-native'; 33 | import React from 'react'; 34 | 35 | const NavigatorBreadcrumbNavigationBarStyles = require('./NavigatorBreadcrumbNavigationBarStyles'); 36 | const NavigatorNavigationBarStylesAndroid = require('./NavigatorNavigationBarStylesAndroid'); 37 | const NavigatorNavigationBarStylesIOS = require('./NavigatorNavigationBarStylesIOS'); 38 | 39 | const guid = require('./guid'); 40 | const invariant = require('fbjs/lib/invariant'); 41 | 42 | const { Map } = require('immutable'); 43 | 44 | const Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators; 45 | const NavigatorNavigationBarStyles = Platform.OS === 'android' ? 46 | NavigatorNavigationBarStylesAndroid : NavigatorNavigationBarStylesIOS; 47 | const PropTypes = React.PropTypes; 48 | 49 | /** 50 | * Reusable props objects. 51 | */ 52 | const CRUMB_PROPS = Interpolators.map(() => ({style: {}})); 53 | const ICON_PROPS = Interpolators.map(() => ({style: {}})); 54 | const SEPARATOR_PROPS = Interpolators.map(() => ({style: {}})); 55 | const TITLE_PROPS = Interpolators.map(() => ({style: {}})); 56 | const RIGHT_BUTTON_PROPS = Interpolators.map(() => ({style: {}})); 57 | 58 | 59 | function navStatePresentedIndex(navState) { 60 | if (navState.presentedIndex !== undefined) { 61 | return navState.presentedIndex; 62 | } 63 | // TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS` 64 | return navState.observedTopOfStack; 65 | } 66 | 67 | 68 | /** 69 | * The first route is initially rendered using a different style than all 70 | * future routes. 71 | * 72 | * @param {number} index Index of breadcrumb. 73 | * @return {object} Style config for initial rendering of index. 74 | */ 75 | function initStyle(index, presentedIndex) { 76 | return index === presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Center[index] : 77 | index < presentedIndex ? NavigatorBreadcrumbNavigationBarStyles.Left[index] : 78 | NavigatorBreadcrumbNavigationBarStyles.Right[index]; 79 | } 80 | 81 | class NavigatorBreadcrumbNavigationBar extends React.Component { 82 | static propTypes = { 83 | navigator: PropTypes.shape({ 84 | push: PropTypes.func, 85 | pop: PropTypes.func, 86 | replace: PropTypes.func, 87 | popToRoute: PropTypes.func, 88 | popToTop: PropTypes.func, 89 | }), 90 | routeMapper: PropTypes.shape({ 91 | rightContentForRoute: PropTypes.func, 92 | titleContentForRoute: PropTypes.func, 93 | iconForRoute: PropTypes.func, 94 | }), 95 | navState: React.PropTypes.shape({ 96 | routeStack: React.PropTypes.arrayOf(React.PropTypes.object), 97 | presentedIndex: React.PropTypes.number, 98 | }), 99 | style: View.propTypes.style, 100 | }; 101 | 102 | static Styles = NavigatorBreadcrumbNavigationBarStyles; 103 | 104 | _updateIndexProgress(progress, index, fromIndex, toIndex) { 105 | var amount = toIndex > fromIndex ? progress : (1 - progress); 106 | var oldDistToCenter = index - fromIndex; 107 | var newDistToCenter = index - toIndex; 108 | var interpolate; 109 | invariant( 110 | Interpolators[index], 111 | 'Cannot find breadcrumb interpolators for ' + index 112 | ); 113 | if (oldDistToCenter > 0 && newDistToCenter === 0 || 114 | newDistToCenter > 0 && oldDistToCenter === 0) { 115 | interpolate = Interpolators[index].RightToCenter; 116 | } else if (oldDistToCenter < 0 && newDistToCenter === 0 || 117 | newDistToCenter < 0 && oldDistToCenter === 0) { 118 | interpolate = Interpolators[index].CenterToLeft; 119 | } else if (oldDistToCenter === newDistToCenter) { 120 | interpolate = Interpolators[index].RightToCenter; 121 | } else { 122 | interpolate = Interpolators[index].RightToLeft; 123 | } 124 | 125 | if (interpolate.Crumb(CRUMB_PROPS[index].style, amount)) { 126 | this._setPropsIfExists('crumb_' + index, CRUMB_PROPS[index]); 127 | } 128 | if (interpolate.Icon(ICON_PROPS[index].style, amount)) { 129 | this._setPropsIfExists('icon_' + index, ICON_PROPS[index]); 130 | } 131 | if (interpolate.Separator(SEPARATOR_PROPS[index].style, amount)) { 132 | this._setPropsIfExists('separator_' + index, SEPARATOR_PROPS[index]); 133 | } 134 | if (interpolate.Title(TITLE_PROPS[index].style, amount)) { 135 | this._setPropsIfExists('title_' + index, TITLE_PROPS[index]); 136 | } 137 | var right = this.refs['right_' + index]; 138 | 139 | const rightButtonStyle = RIGHT_BUTTON_PROPS[index].style; 140 | if (right && interpolate.RightItem(rightButtonStyle, amount)) { 141 | right.setNativeProps({ 142 | style: rightButtonStyle, 143 | pointerEvents: rightButtonStyle.opacity === 0 ? 'none' : 'auto', 144 | }); 145 | } 146 | } 147 | 148 | updateProgress(progress, fromIndex, toIndex) { 149 | var max = Math.max(fromIndex, toIndex); 150 | var min = Math.min(fromIndex, toIndex); 151 | for (var index = min; index <= max; index++) { 152 | this._updateIndexProgress(progress, index, fromIndex, toIndex); 153 | } 154 | } 155 | 156 | onAnimationStart(fromIndex, toIndex) { 157 | var max = Math.max(fromIndex, toIndex); 158 | var min = Math.min(fromIndex, toIndex); 159 | for (var index = min; index <= max; index++) { 160 | this._setRenderViewsToHardwareTextureAndroid(index, true); 161 | } 162 | } 163 | 164 | onAnimationEnd() { 165 | var max = this.props.navState.routeStack.length - 1; 166 | for (var index = 0; index <= max; index++) { 167 | this._setRenderViewsToHardwareTextureAndroid(index, false); 168 | } 169 | } 170 | 171 | _setRenderViewsToHardwareTextureAndroid(index, renderToHardwareTexture) { 172 | var props = { 173 | renderToHardwareTextureAndroid: renderToHardwareTexture, 174 | }; 175 | 176 | this._setPropsIfExists('icon_' + index, props); 177 | this._setPropsIfExists('separator_' + index, props); 178 | this._setPropsIfExists('title_' + index, props); 179 | this._setPropsIfExists('right_' + index, props); 180 | } 181 | 182 | componentWillMount() { 183 | this._reset(); 184 | } 185 | 186 | render() { 187 | var navState = this.props.navState; 188 | var icons = navState && navState.routeStack.map(this._getBreadcrumb); 189 | var titles = navState.routeStack.map(this._getTitle); 190 | var buttons = navState.routeStack.map(this._getRightButton); 191 | 192 | return ( 193 | 196 | {titles} 197 | {icons} 198 | {buttons} 199 | 200 | ); 201 | } 202 | 203 | immediatelyRefresh() { 204 | this._reset(); 205 | this.forceUpdate(); 206 | } 207 | 208 | _reset() { 209 | this._key = guid(); 210 | this._descriptors = { 211 | title: new Map(), 212 | right: new Map(), 213 | }; 214 | } 215 | 216 | _getBreadcrumb = (route, index) => { 217 | /** 218 | * To prevent the case where titles on an empty navigation stack covers the first icon and 219 | * becomes partially unpressable, we set the first breadcrumb to be unpressable by default, and 220 | * make it pressable when there are multiple items in the stack. 221 | */ 222 | const pointerEvents = ( 223 | (this.props.navState.routeStack.length <= 1 && index === 0) ? 224 | 'none' : 225 | 'auto' 226 | ); 227 | const navBarRouteMapper = this.props.routeMapper; 228 | const firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); 229 | 230 | var breadcrumbDescriptor = ( 231 | 236 | 237 | {navBarRouteMapper.iconForRoute(route, this.props.navigator)} 238 | 239 | 240 | {navBarRouteMapper.separatorForRoute(route, this.props.navigator)} 241 | 242 | 243 | ); 244 | 245 | return breadcrumbDescriptor; 246 | }; 247 | 248 | _getTitle = (route, index) => { 249 | if (this._descriptors.title.has(route)) { 250 | return this._descriptors.title.get(route); 251 | } 252 | 253 | var titleContent = this.props.routeMapper.titleContentForRoute( 254 | this.props.navState.routeStack[index], 255 | this.props.navigator 256 | ); 257 | var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); 258 | 259 | var titleDescriptor = ( 260 | 264 | {titleContent} 265 | 266 | ); 267 | this._descriptors.title = this._descriptors.title.set(route, titleDescriptor); 268 | return titleDescriptor; 269 | }; 270 | 271 | _getRightButton = (route, index) => { 272 | if (this._descriptors.right.has(route)) { 273 | return this._descriptors.right.get(route); 274 | } 275 | var rightContent = this.props.routeMapper.rightContentForRoute( 276 | this.props.navState.routeStack[index], 277 | this.props.navigator 278 | ); 279 | if (!rightContent) { 280 | this._descriptors.right = this._descriptors.right.set(route, null); 281 | return null; 282 | } 283 | var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); 284 | var rightButtonDescriptor = ( 285 | 289 | {rightContent} 290 | 291 | ); 292 | this._descriptors.right = this._descriptors.right.set(route, rightButtonDescriptor); 293 | return rightButtonDescriptor; 294 | }; 295 | 296 | _setPropsIfExists(ref, props) { 297 | var ref = this.refs[ref]; 298 | ref && ref.setNativeProps(props); 299 | } 300 | } 301 | 302 | const styles = StyleSheet.create({ 303 | breadCrumbContainer: { 304 | overflow: 'hidden', 305 | position: 'absolute', 306 | height: NavigatorNavigationBarStyles.General.TotalNavHeight, 307 | top: 0, 308 | left: 0, 309 | right: 0, 310 | }, 311 | }); 312 | 313 | module.exports = NavigatorBreadcrumbNavigationBar; 314 | -------------------------------------------------------------------------------- /src/NavigatorBreadcrumbNavigationBarStyles.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | var NavigatorNavigationBarStylesAndroid = require('./NavigatorNavigationBarStylesAndroid'); 29 | 30 | var buildStyleInterpolator = require('./buildStyleInterpolator'); 31 | var merge = require('./merge'); 32 | 33 | var NAV_BAR_HEIGHT = NavigatorNavigationBarStylesAndroid.General.NavBarHeight; 34 | 35 | var SPACING = 8; 36 | var ICON_WIDTH = 40; 37 | var SEPARATOR_WIDTH = 9; 38 | var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH; 39 | var NAV_ELEMENT_HEIGHT = NAV_BAR_HEIGHT; 40 | 41 | var OPACITY_RATIO = 100; 42 | var ICON_INACTIVE_OPACITY = 0.6; 43 | var MAX_BREADCRUMBS = 10; 44 | 45 | var CRUMB_BASE = { 46 | position: 'absolute', 47 | flexDirection: 'row', 48 | top: 0, 49 | width: CRUMB_WIDTH, 50 | height: NAV_ELEMENT_HEIGHT, 51 | backgroundColor: 'transparent', 52 | }; 53 | 54 | var ICON_BASE = { 55 | width: ICON_WIDTH, 56 | height: NAV_ELEMENT_HEIGHT, 57 | }; 58 | 59 | var SEPARATOR_BASE = { 60 | width: SEPARATOR_WIDTH, 61 | height: NAV_ELEMENT_HEIGHT, 62 | }; 63 | 64 | var TITLE_BASE = { 65 | position: 'absolute', 66 | top: 0, 67 | height: NAV_ELEMENT_HEIGHT, 68 | backgroundColor: 'transparent', 69 | alignItems: 'flex-start', 70 | }; 71 | 72 | var FIRST_TITLE_BASE = merge(TITLE_BASE, { 73 | left: 0, 74 | right: 0, 75 | }); 76 | 77 | var RIGHT_BUTTON_BASE = { 78 | position: 'absolute', 79 | top: 0, 80 | right: 0, 81 | overflow: 'hidden', 82 | opacity: 1, 83 | height: NAV_ELEMENT_HEIGHT, 84 | backgroundColor: 'transparent', 85 | }; 86 | 87 | /** 88 | * Precompute crumb styles so that they don't need to be recomputed on every 89 | * interaction. 90 | */ 91 | var LEFT = []; 92 | var CENTER = []; 93 | var RIGHT = []; 94 | for (var i = 0; i < MAX_BREADCRUMBS; i++) { 95 | var crumbLeft = CRUMB_WIDTH * i + SPACING; 96 | LEFT[i] = { 97 | Crumb: merge(CRUMB_BASE, { left: crumbLeft }), 98 | Icon: merge(ICON_BASE, { opacity: ICON_INACTIVE_OPACITY }), 99 | Separator: merge(SEPARATOR_BASE, { opacity: 1 }), 100 | Title: merge(TITLE_BASE, { left: crumbLeft, opacity: 0 }), 101 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }), 102 | }; 103 | CENTER[i] = { 104 | Crumb: merge(CRUMB_BASE, { left: crumbLeft }), 105 | Icon: merge(ICON_BASE, { opacity: 1 }), 106 | Separator: merge(SEPARATOR_BASE, { opacity: 0 }), 107 | Title: merge(TITLE_BASE, { 108 | left: crumbLeft + ICON_WIDTH, 109 | opacity: 1, 110 | }), 111 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 1 }), 112 | }; 113 | var crumbRight = crumbLeft + 50; 114 | RIGHT[i] = { 115 | Crumb: merge(CRUMB_BASE, { left: crumbRight}), 116 | Icon: merge(ICON_BASE, { opacity: 0 }), 117 | Separator: merge(SEPARATOR_BASE, { opacity: 0 }), 118 | Title: merge(TITLE_BASE, { 119 | left: crumbRight + ICON_WIDTH, 120 | opacity: 0, 121 | }), 122 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }), 123 | }; 124 | } 125 | 126 | // Special case the CENTER state of the first scene. 127 | CENTER[0] = { 128 | Crumb: merge(CRUMB_BASE, {left: SPACING + CRUMB_WIDTH}), 129 | Icon: merge(ICON_BASE, {opacity: 0}), 130 | Separator: merge(SEPARATOR_BASE, {opacity: 0}), 131 | Title: merge(FIRST_TITLE_BASE, {opacity: 1}), 132 | RightItem: CENTER[0].RightItem, 133 | }; 134 | LEFT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0}); 135 | RIGHT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0}); 136 | 137 | 138 | var buildIndexSceneInterpolator = function(startStyles, endStyles) { 139 | return { 140 | Crumb: buildStyleInterpolator({ 141 | left: { 142 | type: 'linear', 143 | from: startStyles.Crumb.left, 144 | to: endStyles.Crumb.left, 145 | min: 0, 146 | max: 1, 147 | extrapolate: true, 148 | }, 149 | }), 150 | Icon: buildStyleInterpolator({ 151 | opacity: { 152 | type: 'linear', 153 | from: startStyles.Icon.opacity, 154 | to: endStyles.Icon.opacity, 155 | min: 0, 156 | max: 1, 157 | }, 158 | }), 159 | Separator: buildStyleInterpolator({ 160 | opacity: { 161 | type: 'linear', 162 | from: startStyles.Separator.opacity, 163 | to: endStyles.Separator.opacity, 164 | min: 0, 165 | max: 1, 166 | }, 167 | }), 168 | Title: buildStyleInterpolator({ 169 | opacity: { 170 | type: 'linear', 171 | from: startStyles.Title.opacity, 172 | to: endStyles.Title.opacity, 173 | min: 0, 174 | max: 1, 175 | }, 176 | left: { 177 | type: 'linear', 178 | from: startStyles.Title.left, 179 | to: endStyles.Title.left, 180 | min: 0, 181 | max: 1, 182 | extrapolate: true, 183 | }, 184 | }), 185 | RightItem: buildStyleInterpolator({ 186 | opacity: { 187 | type: 'linear', 188 | from: startStyles.RightItem.opacity, 189 | to: endStyles.RightItem.opacity, 190 | min: 0, 191 | max: 1, 192 | round: OPACITY_RATIO, 193 | }, 194 | }), 195 | }; 196 | }; 197 | 198 | var Interpolators = CENTER.map(function(_, ii) { 199 | return { 200 | // Animating *into* the center stage from the right 201 | RightToCenter: buildIndexSceneInterpolator(RIGHT[ii], CENTER[ii]), 202 | // Animating out of the center stage, to the left 203 | CenterToLeft: buildIndexSceneInterpolator(CENTER[ii], LEFT[ii]), 204 | // Both stages (animating *past* the center stage) 205 | RightToLeft: buildIndexSceneInterpolator(RIGHT[ii], LEFT[ii]), 206 | }; 207 | }); 208 | 209 | /** 210 | * Contains constants that are used in constructing both `StyleSheet`s and 211 | * inline styles during transitions. 212 | */ 213 | module.exports = { 214 | Interpolators, 215 | Left: LEFT, 216 | Center: CENTER, 217 | Right: RIGHT, 218 | IconWidth: ICON_WIDTH, 219 | IconHeight: NAV_BAR_HEIGHT, 220 | SeparatorWidth: SEPARATOR_WIDTH, 221 | SeparatorHeight: NAV_BAR_HEIGHT, 222 | }; 223 | -------------------------------------------------------------------------------- /src/NavigatorBreadcrumbNavigationBarStyles.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | import {Dimensions} from 'react-native'; 29 | var NavigatorNavigationBarStylesIOS = require('./NavigatorNavigationBarStylesIOS'); 30 | 31 | var buildStyleInterpolator = require('./buildStyleInterpolator'); 32 | var merge = require('./merge'); 33 | 34 | var SCREEN_WIDTH = Dimensions.get('window').width; 35 | var STATUS_BAR_HEIGHT = NavigatorNavigationBarStylesIOS.General.StatusBarHeight; 36 | var NAV_BAR_HEIGHT = NavigatorNavigationBarStylesIOS.General.NavBarHeight; 37 | 38 | var SPACING = 4; 39 | var ICON_WIDTH = 40; 40 | var SEPARATOR_WIDTH = 9; 41 | var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH; 42 | 43 | var OPACITY_RATIO = 100; 44 | var ICON_INACTIVE_OPACITY = 0.6; 45 | var MAX_BREADCRUMBS = 10; 46 | 47 | var CRUMB_BASE = { 48 | position: 'absolute', 49 | flexDirection: 'row', 50 | top: STATUS_BAR_HEIGHT, 51 | width: CRUMB_WIDTH, 52 | height: NAV_BAR_HEIGHT, 53 | backgroundColor: 'transparent', 54 | }; 55 | 56 | var ICON_BASE = { 57 | width: ICON_WIDTH, 58 | height: NAV_BAR_HEIGHT, 59 | }; 60 | 61 | var SEPARATOR_BASE = { 62 | width: SEPARATOR_WIDTH, 63 | height: NAV_BAR_HEIGHT, 64 | }; 65 | 66 | var TITLE_BASE = { 67 | position: 'absolute', 68 | top: STATUS_BAR_HEIGHT, 69 | height: NAV_BAR_HEIGHT, 70 | backgroundColor: 'transparent', 71 | }; 72 | 73 | // For first title styles, make sure first title is centered 74 | var FIRST_TITLE_BASE = merge(TITLE_BASE, { 75 | left: 0, 76 | right: 0, 77 | alignItems: 'center', 78 | height: NAV_BAR_HEIGHT, 79 | }); 80 | 81 | var RIGHT_BUTTON_BASE = { 82 | position: 'absolute', 83 | top: STATUS_BAR_HEIGHT, 84 | right: SPACING, 85 | overflow: 'hidden', 86 | opacity: 1, 87 | height: NAV_BAR_HEIGHT, 88 | backgroundColor: 'transparent', 89 | }; 90 | 91 | /** 92 | * Precompute crumb styles so that they don't need to be recomputed on every 93 | * interaction. 94 | */ 95 | var LEFT = []; 96 | var CENTER = []; 97 | var RIGHT = []; 98 | for (var i = 0; i < MAX_BREADCRUMBS; i++) { 99 | var crumbLeft = CRUMB_WIDTH * i + SPACING; 100 | LEFT[i] = { 101 | Crumb: merge(CRUMB_BASE, { left: crumbLeft }), 102 | Icon: merge(ICON_BASE, { opacity: ICON_INACTIVE_OPACITY }), 103 | Separator: merge(SEPARATOR_BASE, { opacity: 1 }), 104 | Title: merge(TITLE_BASE, { left: crumbLeft, opacity: 0 }), 105 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }), 106 | }; 107 | CENTER[i] = { 108 | Crumb: merge(CRUMB_BASE, { left: crumbLeft }), 109 | Icon: merge(ICON_BASE, { opacity: 1 }), 110 | Separator: merge(SEPARATOR_BASE, { opacity: 0 }), 111 | Title: merge(TITLE_BASE, { 112 | left: crumbLeft + ICON_WIDTH, 113 | opacity: 1, 114 | }), 115 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 1 }), 116 | }; 117 | var crumbRight = SCREEN_WIDTH - 100; 118 | RIGHT[i] = { 119 | Crumb: merge(CRUMB_BASE, { left: crumbRight}), 120 | Icon: merge(ICON_BASE, { opacity: 0 }), 121 | Separator: merge(SEPARATOR_BASE, { opacity: 0 }), 122 | Title: merge(TITLE_BASE, { 123 | left: crumbRight + ICON_WIDTH, 124 | opacity: 0, 125 | }), 126 | RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }), 127 | }; 128 | } 129 | 130 | // Special case the CENTER state of the first scene. 131 | CENTER[0] = { 132 | Crumb: merge(CRUMB_BASE, {left: SCREEN_WIDTH / 4}), 133 | Icon: merge(ICON_BASE, {opacity: 0}), 134 | Separator: merge(SEPARATOR_BASE, {opacity: 0}), 135 | Title: merge(FIRST_TITLE_BASE, {opacity: 1}), 136 | RightItem: CENTER[0].RightItem, 137 | }; 138 | LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: -SCREEN_WIDTH / 4, opacity: 0}); 139 | RIGHT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0}); 140 | 141 | 142 | var buildIndexSceneInterpolator = function(startStyles, endStyles) { 143 | return { 144 | Crumb: buildStyleInterpolator({ 145 | left: { 146 | type: 'linear', 147 | from: startStyles.Crumb.left, 148 | to: endStyles.Crumb.left, 149 | min: 0, 150 | max: 1, 151 | extrapolate: true, 152 | }, 153 | }), 154 | Icon: buildStyleInterpolator({ 155 | opacity: { 156 | type: 'linear', 157 | from: startStyles.Icon.opacity, 158 | to: endStyles.Icon.opacity, 159 | min: 0, 160 | max: 1, 161 | }, 162 | }), 163 | Separator: buildStyleInterpolator({ 164 | opacity: { 165 | type: 'linear', 166 | from: startStyles.Separator.opacity, 167 | to: endStyles.Separator.opacity, 168 | min: 0, 169 | max: 1, 170 | }, 171 | }), 172 | Title: buildStyleInterpolator({ 173 | opacity: { 174 | type: 'linear', 175 | from: startStyles.Title.opacity, 176 | to: endStyles.Title.opacity, 177 | min: 0, 178 | max: 1, 179 | }, 180 | left: { 181 | type: 'linear', 182 | from: startStyles.Title.left, 183 | to: endStyles.Title.left, 184 | min: 0, 185 | max: 1, 186 | extrapolate: true, 187 | }, 188 | }), 189 | RightItem: buildStyleInterpolator({ 190 | opacity: { 191 | type: 'linear', 192 | from: startStyles.RightItem.opacity, 193 | to: endStyles.RightItem.opacity, 194 | min: 0, 195 | max: 1, 196 | round: OPACITY_RATIO, 197 | }, 198 | }), 199 | }; 200 | }; 201 | 202 | var Interpolators = CENTER.map(function(_, ii) { 203 | return { 204 | // Animating *into* the center stage from the right 205 | RightToCenter: buildIndexSceneInterpolator(RIGHT[ii], CENTER[ii]), 206 | // Animating out of the center stage, to the left 207 | CenterToLeft: buildIndexSceneInterpolator(CENTER[ii], LEFT[ii]), 208 | // Both stages (animating *past* the center stage) 209 | RightToLeft: buildIndexSceneInterpolator(RIGHT[ii], LEFT[ii]), 210 | }; 211 | }); 212 | 213 | /** 214 | * Contains constants that are used in constructing both `StyleSheet`s and 215 | * inline styles during transitions. 216 | */ 217 | module.exports = { 218 | Interpolators, 219 | Left: LEFT, 220 | Center: CENTER, 221 | Right: RIGHT, 222 | IconWidth: ICON_WIDTH, 223 | IconHeight: NAV_BAR_HEIGHT, 224 | SeparatorWidth: SEPARATOR_WIDTH, 225 | SeparatorHeight: NAV_BAR_HEIGHT, 226 | }; 227 | -------------------------------------------------------------------------------- /src/NavigatorNavigationBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | var React = require('react'); 29 | var NavigatorNavigationBarStylesAndroid = require('./NavigatorNavigationBarStylesAndroid'); 30 | var NavigatorNavigationBarStylesIOS = require('./NavigatorNavigationBarStylesIOS'); 31 | import { 32 | Platform, 33 | StyleSheet, 34 | View, 35 | } from 'react-native'; 36 | 37 | var guid = require('./guid'); 38 | 39 | var { Map } = require('immutable'); 40 | 41 | var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton']; 42 | 43 | var NavigatorNavigationBarStyles = Platform.OS === 'android' ? 44 | NavigatorNavigationBarStylesAndroid : NavigatorNavigationBarStylesIOS; 45 | 46 | var navStatePresentedIndex = function(navState) { 47 | if (navState.presentedIndex !== undefined) { 48 | return navState.presentedIndex; 49 | } 50 | // TODO: rename `observedTopOfStack` to `presentedIndex` in `NavigatorIOS` 51 | return navState.observedTopOfStack; 52 | }; 53 | 54 | class NavigatorNavigationBar extends React.Component { 55 | static propTypes = { 56 | navigator: React.PropTypes.object, 57 | routeMapper: React.PropTypes.shape({ 58 | Title: React.PropTypes.func.isRequired, 59 | LeftButton: React.PropTypes.func.isRequired, 60 | RightButton: React.PropTypes.func.isRequired, 61 | }).isRequired, 62 | navState: React.PropTypes.shape({ 63 | routeStack: React.PropTypes.arrayOf(React.PropTypes.object), 64 | presentedIndex: React.PropTypes.number, 65 | }), 66 | navigationStyles: React.PropTypes.object, 67 | style: View.propTypes.style, 68 | }; 69 | 70 | static Styles = NavigatorNavigationBarStyles; 71 | static StylesAndroid = NavigatorNavigationBarStylesAndroid; 72 | static StylesIOS = NavigatorNavigationBarStylesIOS; 73 | 74 | static defaultProps = { 75 | navigationStyles: NavigatorNavigationBarStyles, 76 | }; 77 | 78 | componentWillMount() { 79 | this._reset(); 80 | } 81 | 82 | /** 83 | * Stop transtion, immediately resets the cached state and re-render the 84 | * whole view. 85 | */ 86 | immediatelyRefresh = () => { 87 | this._reset(); 88 | this.forceUpdate(); 89 | }; 90 | 91 | _reset = () => { 92 | this._key = guid(); 93 | this._reusableProps = {}; 94 | this._components = {}; 95 | this._descriptors = {}; 96 | 97 | COMPONENT_NAMES.forEach(componentName => { 98 | this._components[componentName] = new Map(); 99 | this._descriptors[componentName] = new Map(); 100 | }); 101 | }; 102 | 103 | _getReusableProps = (/*string*/componentName, /*number*/index) => /*object*/ { 104 | var propStack = this._reusableProps[componentName]; 105 | if (!propStack) { 106 | propStack = this._reusableProps[componentName] = []; 107 | } 108 | var props = propStack[index]; 109 | if (!props) { 110 | props = propStack[index] = {style:{}}; 111 | } 112 | return props; 113 | }; 114 | 115 | _updateIndexProgress = ( 116 | /*number*/progress, 117 | /*number*/index, 118 | /*number*/fromIndex, 119 | /*number*/toIndex, 120 | ) => { 121 | var amount = toIndex > fromIndex ? progress : (1 - progress); 122 | var oldDistToCenter = index - fromIndex; 123 | var newDistToCenter = index - toIndex; 124 | var interpolate; 125 | if (oldDistToCenter > 0 && newDistToCenter === 0 || 126 | newDistToCenter > 0 && oldDistToCenter === 0) { 127 | interpolate = this.props.navigationStyles.Interpolators.RightToCenter; 128 | } else if (oldDistToCenter < 0 && newDistToCenter === 0 || 129 | newDistToCenter < 0 && oldDistToCenter === 0) { 130 | interpolate = this.props.navigationStyles.Interpolators.CenterToLeft; 131 | } else if (oldDistToCenter === newDistToCenter) { 132 | interpolate = this.props.navigationStyles.Interpolators.RightToCenter; 133 | } else { 134 | interpolate = this.props.navigationStyles.Interpolators.RightToLeft; 135 | } 136 | 137 | COMPONENT_NAMES.forEach(function (componentName) { 138 | var component = this._components[componentName].get(this.props.navState.routeStack[index]); 139 | var props = this._getReusableProps(componentName, index); 140 | if (component && interpolate[componentName](props.style, amount)) { 141 | props.pointerEvents = props.style.opacity === 0 ? 'none' : 'box-none'; 142 | component.setNativeProps(props); 143 | } 144 | }, this); 145 | }; 146 | 147 | updateProgress = (/*number*/progress, /*number*/fromIndex, /*number*/toIndex) => { 148 | var max = Math.max(fromIndex, toIndex); 149 | var min = Math.min(fromIndex, toIndex); 150 | for (var index = min; index <= max; index++) { 151 | this._updateIndexProgress(progress, index, fromIndex, toIndex); 152 | } 153 | }; 154 | 155 | render() { 156 | var navBarStyle = { 157 | height: this.props.navigationStyles.General.TotalNavHeight, 158 | }; 159 | var navState = this.props.navState; 160 | var components = navState.routeStack.map((route, index) => 161 | COMPONENT_NAMES.map(componentName => 162 | this._getComponent(componentName, route, index) 163 | ) 164 | ); 165 | 166 | return ( 167 | 170 | {components} 171 | 172 | ); 173 | } 174 | 175 | _getComponent = (/*string*/componentName, /*object*/route, /*number*/index) => /*?Object*/ { 176 | if (this._descriptors[componentName].includes(route)) { 177 | return this._descriptors[componentName].get(route); 178 | } 179 | 180 | var rendered = null; 181 | 182 | var content = this.props.routeMapper[componentName]( 183 | this.props.navState.routeStack[index], 184 | this.props.navigator, 185 | index, 186 | this.props.navState 187 | ); 188 | if (!content) { 189 | return null; 190 | } 191 | 192 | var componentIsActive = index === navStatePresentedIndex(this.props.navState); 193 | var initialStage = componentIsActive ? 194 | this.props.navigationStyles.Stages.Center : 195 | this.props.navigationStyles.Stages.Left; 196 | rendered = ( 197 | { 199 | this._components[componentName] = this._components[componentName].set(route, ref); 200 | }} 201 | pointerEvents={componentIsActive ? 'box-none' : 'none'} 202 | style={initialStage[componentName]}> 203 | {content} 204 | 205 | ); 206 | 207 | this._descriptors[componentName] = this._descriptors[componentName].set(route, rendered); 208 | return rendered; 209 | }; 210 | } 211 | 212 | 213 | var styles = StyleSheet.create({ 214 | navBarContainer: { 215 | position: 'absolute', 216 | top: 0, 217 | left: 0, 218 | right: 0, 219 | backgroundColor: 'transparent', 220 | }, 221 | }); 222 | 223 | module.exports = NavigatorNavigationBar; 224 | -------------------------------------------------------------------------------- /src/NavigatorNavigationBarStylesAndroid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | var buildStyleInterpolator = require('./buildStyleInterpolator'); 29 | var merge = require('./merge'); 30 | 31 | // Android Material Design 32 | var NAV_BAR_HEIGHT = 56; 33 | var TITLE_LEFT = 72; 34 | var BUTTON_SIZE = 24; 35 | var TOUCH_TARGT_SIZE = 48; 36 | var BUTTON_HORIZONTAL_MARGIN = 16; 37 | 38 | var BUTTON_EFFECTIVE_MARGIN = BUTTON_HORIZONTAL_MARGIN - (TOUCH_TARGT_SIZE - BUTTON_SIZE) / 2; 39 | var NAV_ELEMENT_HEIGHT = NAV_BAR_HEIGHT; 40 | 41 | var BASE_STYLES = { 42 | Title: { 43 | position: 'absolute', 44 | bottom: 0, 45 | left: 0, 46 | right: 0, 47 | alignItems: 'flex-start', 48 | height: NAV_ELEMENT_HEIGHT, 49 | backgroundColor: 'transparent', 50 | marginLeft: TITLE_LEFT, 51 | }, 52 | LeftButton: { 53 | position: 'absolute', 54 | top: 0, 55 | left: BUTTON_EFFECTIVE_MARGIN, 56 | overflow: 'hidden', 57 | height: NAV_ELEMENT_HEIGHT, 58 | backgroundColor: 'transparent', 59 | }, 60 | RightButton: { 61 | position: 'absolute', 62 | top: 0, 63 | right: BUTTON_EFFECTIVE_MARGIN, 64 | overflow: 'hidden', 65 | alignItems: 'flex-end', 66 | height: NAV_ELEMENT_HEIGHT, 67 | backgroundColor: 'transparent', 68 | }, 69 | }; 70 | 71 | // There are 3 stages: left, center, right. All previous navigation 72 | // items are in the left stage. The current navigation item is in the 73 | // center stage. All upcoming navigation items are in the right stage. 74 | // Another way to think of the stages is in terms of transitions. When 75 | // we move forward in the navigation stack, we perform a 76 | // right-to-center transition on the new navigation item and a 77 | // center-to-left transition on the current navigation item. 78 | var Stages = { 79 | Left: { 80 | Title: merge(BASE_STYLES.Title, { opacity: 0 }), 81 | LeftButton: merge(BASE_STYLES.LeftButton, { opacity: 0 }), 82 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }), 83 | }, 84 | Center: { 85 | Title: merge(BASE_STYLES.Title, { opacity: 1 }), 86 | LeftButton: merge(BASE_STYLES.LeftButton, { opacity: 1 }), 87 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 1 }), 88 | }, 89 | Right: { 90 | Title: merge(BASE_STYLES.Title, { opacity: 0 }), 91 | LeftButton: merge(BASE_STYLES.LeftButton, { opacity: 0 }), 92 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }), 93 | }, 94 | }; 95 | 96 | 97 | var opacityRatio = 100; 98 | 99 | function buildSceneInterpolators(startStyles, endStyles) { 100 | return { 101 | Title: buildStyleInterpolator({ 102 | opacity: { 103 | type: 'linear', 104 | from: startStyles.Title.opacity, 105 | to: endStyles.Title.opacity, 106 | min: 0, 107 | max: 1, 108 | }, 109 | left: { 110 | type: 'linear', 111 | from: startStyles.Title.left, 112 | to: endStyles.Title.left, 113 | min: 0, 114 | max: 1, 115 | extrapolate: true, 116 | }, 117 | }), 118 | LeftButton: buildStyleInterpolator({ 119 | opacity: { 120 | type: 'linear', 121 | from: startStyles.LeftButton.opacity, 122 | to: endStyles.LeftButton.opacity, 123 | min: 0, 124 | max: 1, 125 | round: opacityRatio, 126 | }, 127 | left: { 128 | type: 'linear', 129 | from: startStyles.LeftButton.left, 130 | to: endStyles.LeftButton.left, 131 | min: 0, 132 | max: 1, 133 | }, 134 | }), 135 | RightButton: buildStyleInterpolator({ 136 | opacity: { 137 | type: 'linear', 138 | from: startStyles.RightButton.opacity, 139 | to: endStyles.RightButton.opacity, 140 | min: 0, 141 | max: 1, 142 | round: opacityRatio, 143 | }, 144 | left: { 145 | type: 'linear', 146 | from: startStyles.RightButton.left, 147 | to: endStyles.RightButton.left, 148 | min: 0, 149 | max: 1, 150 | extrapolate: true, 151 | }, 152 | }), 153 | }; 154 | } 155 | 156 | var Interpolators = { 157 | // Animating *into* the center stage from the right 158 | RightToCenter: buildSceneInterpolators(Stages.Right, Stages.Center), 159 | // Animating out of the center stage, to the left 160 | CenterToLeft: buildSceneInterpolators(Stages.Center, Stages.Left), 161 | // Both stages (animating *past* the center stage) 162 | RightToLeft: buildSceneInterpolators(Stages.Right, Stages.Left), 163 | }; 164 | 165 | 166 | module.exports = { 167 | General: { 168 | NavBarHeight: NAV_BAR_HEIGHT, 169 | StatusBarHeight: 0, 170 | TotalNavHeight: NAV_BAR_HEIGHT, 171 | }, 172 | Interpolators, 173 | Stages, 174 | }; 175 | -------------------------------------------------------------------------------- /src/NavigatorNavigationBarStylesIOS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | import { 29 | Dimensions, 30 | I18nManager, 31 | PixelRatio, 32 | } from 'react-native'; 33 | 34 | var buildStyleInterpolator = require('./buildStyleInterpolator'); 35 | var merge = require('./merge'); 36 | 37 | var SCREEN_WIDTH = Dimensions.get('window').width; 38 | var NAV_BAR_HEIGHT = 44; 39 | var STATUS_BAR_HEIGHT = 20; 40 | var NAV_HEIGHT = NAV_BAR_HEIGHT + STATUS_BAR_HEIGHT; 41 | 42 | var BASE_STYLES = { 43 | Title: { 44 | position: 'absolute', 45 | top: STATUS_BAR_HEIGHT, 46 | left: 0, 47 | right: 0, 48 | alignItems: 'center', 49 | height: NAV_BAR_HEIGHT, 50 | backgroundColor: 'transparent', 51 | }, 52 | LeftButton: { 53 | position: 'absolute', 54 | top: STATUS_BAR_HEIGHT, 55 | left: 0, 56 | overflow: 'hidden', 57 | opacity: 1, 58 | height: NAV_BAR_HEIGHT, 59 | backgroundColor: 'transparent', 60 | }, 61 | RightButton: { 62 | position: 'absolute', 63 | top: STATUS_BAR_HEIGHT, 64 | right: 0, 65 | overflow: 'hidden', 66 | opacity: 1, 67 | alignItems: 'flex-end', 68 | height: NAV_BAR_HEIGHT, 69 | backgroundColor: 'transparent', 70 | }, 71 | }; 72 | 73 | // There are 3 stages: left, center, right. All previous navigation 74 | // items are in the left stage. The current navigation item is in the 75 | // center stage. All upcoming navigation items are in the right stage. 76 | // Another way to think of the stages is in terms of transitions. When 77 | // we move forward in the navigation stack, we perform a 78 | // right-to-center transition on the new navigation item and a 79 | // center-to-left transition on the current navigation item. 80 | var Stages = { 81 | Left: { 82 | Title: merge(BASE_STYLES.Title, { left: -SCREEN_WIDTH / 2, opacity: 0 }), 83 | LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 0 }), 84 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }), 85 | }, 86 | Center: { 87 | Title: merge(BASE_STYLES.Title, { left: 0, opacity: 1 }), 88 | LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 1 }), 89 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 1 }), 90 | }, 91 | Right: { 92 | Title: merge(BASE_STYLES.Title, { left: SCREEN_WIDTH / 2, opacity: 0 }), 93 | LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 0 }), 94 | RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }), 95 | }, 96 | }; 97 | 98 | 99 | var opacityRatio = 100; 100 | 101 | function buildSceneInterpolators(startStyles, endStyles) { 102 | return { 103 | Title: buildStyleInterpolator({ 104 | opacity: { 105 | type: 'linear', 106 | from: startStyles.Title.opacity, 107 | to: endStyles.Title.opacity, 108 | min: 0, 109 | max: 1, 110 | }, 111 | left: { 112 | type: 'linear', 113 | from: startStyles.Title.left, 114 | to: endStyles.Title.left, 115 | min: 0, 116 | max: 1, 117 | extrapolate: true, 118 | }, 119 | }), 120 | LeftButton: buildStyleInterpolator({ 121 | opacity: { 122 | type: 'linear', 123 | from: startStyles.LeftButton.opacity, 124 | to: endStyles.LeftButton.opacity, 125 | min: 0, 126 | max: 1, 127 | round: opacityRatio, 128 | }, 129 | left: { 130 | type: 'linear', 131 | from: startStyles.LeftButton.left, 132 | to: endStyles.LeftButton.left, 133 | min: 0, 134 | max: 1, 135 | }, 136 | }), 137 | RightButton: buildStyleInterpolator({ 138 | opacity: { 139 | type: 'linear', 140 | from: startStyles.RightButton.opacity, 141 | to: endStyles.RightButton.opacity, 142 | min: 0, 143 | max: 1, 144 | round: opacityRatio, 145 | }, 146 | left: { 147 | type: 'linear', 148 | from: startStyles.RightButton.left, 149 | to: endStyles.RightButton.left, 150 | min: 0, 151 | max: 1, 152 | extrapolate: true, 153 | }, 154 | }), 155 | }; 156 | } 157 | 158 | var Interpolators = { 159 | // Animating *into* the center stage from the right 160 | RightToCenter: buildSceneInterpolators(Stages.Right, Stages.Center), 161 | // Animating out of the center stage, to the left 162 | CenterToLeft: buildSceneInterpolators(Stages.Center, Stages.Left), 163 | // Both stages (animating *past* the center stage) 164 | RightToLeft: buildSceneInterpolators(Stages.Right, Stages.Left), 165 | }; 166 | 167 | 168 | module.exports = { 169 | General: { 170 | NavBarHeight: NAV_BAR_HEIGHT, 171 | StatusBarHeight: STATUS_BAR_HEIGHT, 172 | TotalNavHeight: NAV_HEIGHT, 173 | }, 174 | Interpolators, 175 | Stages, 176 | }; 177 | -------------------------------------------------------------------------------- /src/NavigatorSceneConfigs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 'use strict'; 27 | 28 | import { 29 | Dimensions, 30 | I18nManager, 31 | PixelRatio, 32 | } from 'react-native'; 33 | 34 | var buildStyleInterpolator = require('./buildStyleInterpolator'); 35 | 36 | var IS_RTL = I18nManager.isRTL; 37 | 38 | var SCREEN_WIDTH = Dimensions.get('window').width; 39 | var SCREEN_HEIGHT = Dimensions.get('window').height; 40 | var PIXEL_RATIO = PixelRatio.get(); 41 | 42 | var ToTheLeftIOS = { 43 | transformTranslate: { 44 | from: {x: 0, y: 0, z: 0}, 45 | to: {x: -SCREEN_WIDTH * 0.3, y: 0, z: 0}, 46 | min: 0, 47 | max: 1, 48 | type: 'linear', 49 | extrapolate: true, 50 | round: PIXEL_RATIO, 51 | }, 52 | opacity: { 53 | value: 1.0, 54 | type: 'constant', 55 | }, 56 | }; 57 | 58 | var ToTheRightIOS = { 59 | ...ToTheLeftIOS, 60 | transformTranslate: { 61 | from: {x: 0, y: 0, z: 0}, 62 | to: {x: SCREEN_WIDTH * 0.3, y: 0, z: 0}, 63 | }, 64 | }; 65 | 66 | var FadeToTheLeft = { 67 | // Rotate *requires* you to break out each individual component of 68 | // rotation (x, y, z, w) 69 | transformTranslate: { 70 | from: {x: 0, y: 0, z: 0}, 71 | to: {x: -Math.round(SCREEN_WIDTH * 0.3), y: 0, z: 0}, 72 | min: 0, 73 | max: 1, 74 | type: 'linear', 75 | extrapolate: true, 76 | round: PIXEL_RATIO, 77 | }, 78 | // Uncomment to try rotation: 79 | // Quick guide to reasoning about rotations: 80 | // http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#Quaternions 81 | // transformRotateRadians: { 82 | // from: {x: 0, y: 0, z: 0, w: 1}, 83 | // to: {x: 0, y: 0, z: -0.47, w: 0.87}, 84 | // min: 0, 85 | // max: 1, 86 | // type: 'linear', 87 | // extrapolate: true 88 | // }, 89 | transformScale: { 90 | from: {x: 1, y: 1, z: 1}, 91 | to: {x: 0.95, y: 0.95, z: 1}, 92 | min: 0, 93 | max: 1, 94 | type: 'linear', 95 | extrapolate: true 96 | }, 97 | opacity: { 98 | from: 1, 99 | to: 0.3, 100 | min: 0, 101 | max: 1, 102 | type: 'linear', 103 | extrapolate: false, 104 | round: 100, 105 | }, 106 | translateX: { 107 | from: 0, 108 | to: -Math.round(SCREEN_WIDTH * 0.3), 109 | min: 0, 110 | max: 1, 111 | type: 'linear', 112 | extrapolate: true, 113 | round: PIXEL_RATIO, 114 | }, 115 | scaleX: { 116 | from: 1, 117 | to: 0.95, 118 | min: 0, 119 | max: 1, 120 | type: 'linear', 121 | extrapolate: true 122 | }, 123 | scaleY: { 124 | from: 1, 125 | to: 0.95, 126 | min: 0, 127 | max: 1, 128 | type: 'linear', 129 | extrapolate: true 130 | }, 131 | }; 132 | 133 | var FadeToTheRight = { 134 | ...FadeToTheLeft, 135 | transformTranslate: { 136 | from: {x: 0, y: 0, z: 0}, 137 | to: {x: Math.round(SCREEN_WIDTH * 0.3), y: 0, z: 0}, 138 | }, 139 | translateX: { 140 | from: 0, 141 | to: Math.round(SCREEN_WIDTH * 0.3), 142 | }, 143 | }; 144 | 145 | var FadeIn = { 146 | opacity: { 147 | from: 0, 148 | to: 1, 149 | min: 0.5, 150 | max: 1, 151 | type: 'linear', 152 | extrapolate: false, 153 | round: 100, 154 | }, 155 | }; 156 | 157 | var FadeOut = { 158 | opacity: { 159 | from: 1, 160 | to: 0, 161 | min: 0, 162 | max: 0.5, 163 | type: 'linear', 164 | extrapolate: false, 165 | round: 100, 166 | }, 167 | }; 168 | 169 | var ToTheLeft = { 170 | transformTranslate: { 171 | from: {x: 0, y: 0, z: 0}, 172 | to: {x: -SCREEN_WIDTH, y: 0, z: 0}, 173 | min: 0, 174 | max: 1, 175 | type: 'linear', 176 | extrapolate: true, 177 | round: PIXEL_RATIO, 178 | }, 179 | opacity: { 180 | value: 1.0, 181 | type: 'constant', 182 | }, 183 | 184 | translateX: { 185 | from: 0, 186 | to: -SCREEN_WIDTH, 187 | min: 0, 188 | max: 1, 189 | type: 'linear', 190 | extrapolate: true, 191 | round: PIXEL_RATIO, 192 | }, 193 | }; 194 | 195 | var ToTheRight = { 196 | transformTranslate: { 197 | from: {x: 0, y: 0, z: 0}, 198 | to: {x: SCREEN_WIDTH, y: 0, z: 0}, 199 | min: 0, 200 | max: 1, 201 | type: 'linear', 202 | extrapolate: true, 203 | round: PIXEL_RATIO, 204 | }, 205 | opacity: { 206 | value: 1.0, 207 | type: 'constant', 208 | }, 209 | 210 | translateX: { 211 | from: 0, 212 | to: SCREEN_WIDTH, 213 | min: 0, 214 | max: 1, 215 | type: 'linear', 216 | extrapolate: true, 217 | round: PIXEL_RATIO, 218 | }, 219 | }; 220 | 221 | var ToTheUp = { 222 | transformTranslate: { 223 | from: {x: 0, y: 0, z: 0}, 224 | to: {x: 0, y: -SCREEN_HEIGHT, z: 0}, 225 | min: 0, 226 | max: 1, 227 | type: 'linear', 228 | extrapolate: true, 229 | round: PIXEL_RATIO, 230 | }, 231 | opacity: { 232 | value: 1.0, 233 | type: 'constant', 234 | }, 235 | translateY: { 236 | from: 0, 237 | to: -SCREEN_HEIGHT, 238 | min: 0, 239 | max: 1, 240 | type: 'linear', 241 | extrapolate: true, 242 | round: PIXEL_RATIO, 243 | }, 244 | }; 245 | 246 | var ToTheDown = { 247 | transformTranslate: { 248 | from: {x: 0, y: 0, z: 0}, 249 | to: {x: 0, y: SCREEN_HEIGHT, z: 0}, 250 | min: 0, 251 | max: 1, 252 | type: 'linear', 253 | extrapolate: true, 254 | round: PIXEL_RATIO, 255 | }, 256 | opacity: { 257 | value: 1.0, 258 | type: 'constant', 259 | }, 260 | translateY: { 261 | from: 0, 262 | to: SCREEN_HEIGHT, 263 | min: 0, 264 | max: 1, 265 | type: 'linear', 266 | extrapolate: true, 267 | round: PIXEL_RATIO, 268 | }, 269 | }; 270 | 271 | var FromTheRight = { 272 | opacity: { 273 | value: 1.0, 274 | type: 'constant', 275 | }, 276 | 277 | transformTranslate: { 278 | from: {x: SCREEN_WIDTH, y: 0, z: 0}, 279 | to: {x: 0, y: 0, z: 0}, 280 | min: 0, 281 | max: 1, 282 | type: 'linear', 283 | extrapolate: true, 284 | round: PIXEL_RATIO, 285 | }, 286 | 287 | translateX: { 288 | from: SCREEN_WIDTH, 289 | to: 0, 290 | min: 0, 291 | max: 1, 292 | type: 'linear', 293 | extrapolate: true, 294 | round: PIXEL_RATIO, 295 | }, 296 | 297 | scaleX: { 298 | value: 1, 299 | type: 'constant', 300 | }, 301 | scaleY: { 302 | value: 1, 303 | type: 'constant', 304 | }, 305 | }; 306 | 307 | var FromTheLeft = { 308 | ...FromTheRight, 309 | transformTranslate: { 310 | from: {x: -SCREEN_WIDTH, y: 0, z: 0}, 311 | to: {x: 0, y: 0, z: 0}, 312 | min: 0, 313 | max: 1, 314 | type: 'linear', 315 | extrapolate: true, 316 | round: PIXEL_RATIO, 317 | }, 318 | translateX: { 319 | from: -SCREEN_WIDTH, 320 | to: 0, 321 | min: 0, 322 | max: 1, 323 | type: 'linear', 324 | extrapolate: true, 325 | round: PIXEL_RATIO, 326 | }, 327 | }; 328 | 329 | var FromTheDown = { 330 | ...FromTheRight, 331 | transformTranslate: { 332 | from: {y: SCREEN_HEIGHT, x: 0, z: 0}, 333 | to: {x: 0, y: 0, z: 0}, 334 | min: 0, 335 | max: 1, 336 | type: 'linear', 337 | extrapolate: true, 338 | round: PIXEL_RATIO, 339 | }, 340 | translateY: { 341 | from: SCREEN_HEIGHT, 342 | to: 0, 343 | min: 0, 344 | max: 1, 345 | type: 'linear', 346 | extrapolate: true, 347 | round: PIXEL_RATIO, 348 | }, 349 | }; 350 | 351 | var FromTheTop = { 352 | ...FromTheRight, 353 | transformTranslate: { 354 | from: {y: -SCREEN_HEIGHT, x: 0, z: 0}, 355 | to: {x: 0, y: 0, z: 0}, 356 | min: 0, 357 | max: 1, 358 | type: 'linear', 359 | extrapolate: true, 360 | round: PIXEL_RATIO, 361 | }, 362 | translateY: { 363 | from: -SCREEN_HEIGHT, 364 | to: 0, 365 | min: 0, 366 | max: 1, 367 | type: 'linear', 368 | extrapolate: true, 369 | round: PIXEL_RATIO, 370 | }, 371 | }; 372 | 373 | var ToTheBack = { 374 | // Rotate *requires* you to break out each individual component of 375 | // rotation (x, y, z, w) 376 | transformTranslate: { 377 | from: {x: 0, y: 0, z: 0}, 378 | to: {x: 0, y: 0, z: 0}, 379 | min: 0, 380 | max: 1, 381 | type: 'linear', 382 | extrapolate: true, 383 | round: PIXEL_RATIO, 384 | }, 385 | transformScale: { 386 | from: {x: 1, y: 1, z: 1}, 387 | to: {x: 0.95, y: 0.95, z: 1}, 388 | min: 0, 389 | max: 1, 390 | type: 'linear', 391 | extrapolate: true 392 | }, 393 | opacity: { 394 | from: 1, 395 | to: 0.3, 396 | min: 0, 397 | max: 1, 398 | type: 'linear', 399 | extrapolate: false, 400 | round: 100, 401 | }, 402 | scaleX: { 403 | from: 1, 404 | to: 0.95, 405 | min: 0, 406 | max: 1, 407 | type: 'linear', 408 | extrapolate: true 409 | }, 410 | scaleY: { 411 | from: 1, 412 | to: 0.95, 413 | min: 0, 414 | max: 1, 415 | type: 'linear', 416 | extrapolate: true 417 | }, 418 | }; 419 | 420 | var FromTheFront = { 421 | opacity: { 422 | value: 1.0, 423 | type: 'constant', 424 | }, 425 | 426 | transformTranslate: { 427 | from: {x: 0, y: SCREEN_HEIGHT, z: 0}, 428 | to: {x: 0, y: 0, z: 0}, 429 | min: 0, 430 | max: 1, 431 | type: 'linear', 432 | extrapolate: true, 433 | round: PIXEL_RATIO, 434 | }, 435 | translateY: { 436 | from: SCREEN_HEIGHT, 437 | to: 0, 438 | min: 0, 439 | max: 1, 440 | type: 'linear', 441 | extrapolate: true, 442 | round: PIXEL_RATIO, 443 | }, 444 | scaleX: { 445 | value: 1, 446 | type: 'constant', 447 | }, 448 | scaleY: { 449 | value: 1, 450 | type: 'constant', 451 | }, 452 | }; 453 | 454 | var ToTheBackAndroid = { 455 | opacity: { 456 | value: 1, 457 | type: 'constant', 458 | }, 459 | }; 460 | 461 | var FromTheFrontAndroid = { 462 | opacity: { 463 | from: 0, 464 | to: 1, 465 | min: 0.5, 466 | max: 1, 467 | type: 'linear', 468 | extrapolate: false, 469 | round: 100, 470 | }, 471 | transformTranslate: { 472 | from: {x: 0, y: 100, z: 0}, 473 | to: {x: 0, y: 0, z: 0}, 474 | min: 0, 475 | max: 1, 476 | type: 'linear', 477 | extrapolate: true, 478 | round: PIXEL_RATIO, 479 | }, 480 | translateY: { 481 | from: 100, 482 | to: 0, 483 | min: 0, 484 | max: 1, 485 | type: 'linear', 486 | extrapolate: true, 487 | round: PIXEL_RATIO, 488 | }, 489 | }; 490 | 491 | var BaseOverswipeConfig = { 492 | frictionConstant: 1, 493 | frictionByDistance: 1.5, 494 | }; 495 | 496 | var BaseLeftToRightGesture = { 497 | 498 | // If the gesture can end and restart during one continuous touch 499 | isDetachable: false, 500 | 501 | // How far the swipe must drag to start transitioning 502 | gestureDetectMovement: 2, 503 | 504 | // Amplitude of release velocity that is considered still 505 | notMoving: 0.3, 506 | 507 | // Fraction of directional move required. 508 | directionRatio: 0.66, 509 | 510 | // Velocity to transition with when the gesture release was "not moving" 511 | snapVelocity: 2, 512 | 513 | // Region that can trigger swipe. iOS default is 30px from the left edge 514 | edgeHitWidth: 30, 515 | 516 | // Ratio of gesture completion when non-velocity release will cause action 517 | stillCompletionRatio: 3 / 5, 518 | 519 | fullDistance: SCREEN_WIDTH, 520 | 521 | direction: 'left-to-right', 522 | 523 | }; 524 | 525 | var BaseRightToLeftGesture = { 526 | ...BaseLeftToRightGesture, 527 | direction: 'right-to-left', 528 | }; 529 | 530 | var BaseDownUpGesture = { 531 | ...BaseLeftToRightGesture, 532 | fullDistance: SCREEN_HEIGHT, 533 | direction: 'bottom-to-top', 534 | }; 535 | 536 | var BaseUpDownGesture = { 537 | ...BaseLeftToRightGesture, 538 | fullDistance: SCREEN_HEIGHT, 539 | direction: 'top-to-bottom', 540 | }; 541 | 542 | // For RTL experiment, we need to swap all the Left and Right gesture and animation. 543 | // So we create a direction mapping for both LTR and RTL, and change left/right to start/end. 544 | let directionMapping = { 545 | ToTheStartIOS: ToTheLeftIOS, 546 | ToTheEndIOS: ToTheRightIOS, 547 | FadeToTheStart: FadeToTheLeft, 548 | FadeToTheEnd: FadeToTheRight, 549 | ToTheStart: ToTheLeft, 550 | ToTheEnd: ToTheRight, 551 | FromTheStart: FromTheLeft, 552 | FromTheEnd: FromTheRight, 553 | BaseStartToEndGesture: BaseLeftToRightGesture, 554 | BaseEndToStartGesture: BaseRightToLeftGesture, 555 | }; 556 | 557 | if (IS_RTL) { 558 | directionMapping = { 559 | ToTheStartIOS: ToTheRightIOS, 560 | ToTheEndIOS: ToTheLeftIOS, 561 | FadeToTheStart: FadeToTheRight, 562 | FadeToTheEnd: FadeToTheLeft, 563 | ToTheStart: ToTheRight, 564 | ToTheEnd: ToTheLeft, 565 | FromTheStart: FromTheRight, 566 | FromTheEnd: FromTheLeft, 567 | BaseStartToEndGesture: BaseRightToLeftGesture, 568 | BaseEndToStartGesture: BaseLeftToRightGesture, 569 | }; 570 | } 571 | 572 | var BaseConfig = { 573 | // A list of all gestures that are enabled on this scene 574 | gestures: { 575 | pop: directionMapping.BaseStartToEndGesture, 576 | }, 577 | 578 | // Rebound spring parameters when transitioning FROM this scene 579 | springFriction: 26, 580 | springTension: 200, 581 | 582 | // Velocity to start at when transitioning without gesture 583 | defaultTransitionVelocity: 1.5, 584 | 585 | // Animation interpolators for horizontal transitioning: 586 | animationInterpolators: { 587 | into: buildStyleInterpolator(directionMapping.FromTheEnd), 588 | out: buildStyleInterpolator(directionMapping.FadeToTheStart), 589 | }, 590 | }; 591 | 592 | var NavigatorSceneConfigs = { 593 | PushFromRight: { 594 | ...BaseConfig, 595 | animationInterpolators: { 596 | into: buildStyleInterpolator(directionMapping.FromTheEnd), 597 | out: buildStyleInterpolator(directionMapping.ToTheStartIOS), 598 | }, 599 | }, 600 | PushFromLeft: { 601 | ...BaseConfig, 602 | animationInterpolators: { 603 | into: buildStyleInterpolator(directionMapping.FromTheStart), 604 | out: buildStyleInterpolator(directionMapping.ToTheEndIOS), 605 | }, 606 | }, 607 | FloatFromRight: { 608 | ...BaseConfig, 609 | // We will want to customize this soon 610 | }, 611 | FloatFromLeft: { 612 | ...BaseConfig, 613 | gestures: { 614 | pop: directionMapping.BaseEndToStartGesture, 615 | }, 616 | animationInterpolators: { 617 | into: buildStyleInterpolator(directionMapping.FromTheStart), 618 | out: buildStyleInterpolator(directionMapping.FadeToTheEnd), 619 | }, 620 | }, 621 | FloatFromBottom: { 622 | ...BaseConfig, 623 | gestures: { 624 | pop: { 625 | ...directionMapping.BaseStartToEndGesture, 626 | edgeHitWidth: 150, 627 | direction: 'top-to-bottom', 628 | fullDistance: SCREEN_HEIGHT, 629 | } 630 | }, 631 | animationInterpolators: { 632 | into: buildStyleInterpolator(FromTheFront), 633 | out: buildStyleInterpolator(ToTheBack), 634 | }, 635 | }, 636 | FloatFromBottomAndroid: { 637 | ...BaseConfig, 638 | gestures: null, 639 | defaultTransitionVelocity: 3, 640 | springFriction: 20, 641 | animationInterpolators: { 642 | into: buildStyleInterpolator(FromTheFrontAndroid), 643 | out: buildStyleInterpolator(ToTheBackAndroid), 644 | }, 645 | }, 646 | FadeAndroid: { 647 | ...BaseConfig, 648 | gestures: null, 649 | animationInterpolators: { 650 | into: buildStyleInterpolator(FadeIn), 651 | out: buildStyleInterpolator(FadeOut), 652 | }, 653 | }, 654 | SwipeFromLeft: { 655 | ...BaseConfig, 656 | gestures: { 657 | jumpBack: { 658 | ...directionMapping.BaseEndToStartGesture, 659 | overswipe: BaseOverswipeConfig, 660 | edgeHitWidth: null, 661 | isDetachable: true, 662 | }, 663 | jumpForward: { 664 | ...directionMapping.BaseStartToEndGesture, 665 | overswipe: BaseOverswipeConfig, 666 | edgeHitWidth: null, 667 | isDetachable: true, 668 | }, 669 | }, 670 | animationInterpolators: { 671 | into: buildStyleInterpolator(directionMapping.FromTheStart), 672 | out: buildStyleInterpolator(directionMapping.ToTheEnd), 673 | }, 674 | }, 675 | HorizontalSwipeJump: { 676 | ...BaseConfig, 677 | gestures: { 678 | jumpBack: { 679 | ...directionMapping.BaseStartToEndGesture, 680 | overswipe: BaseOverswipeConfig, 681 | edgeHitWidth: null, 682 | isDetachable: true, 683 | }, 684 | jumpForward: { 685 | ...directionMapping.BaseEndToStartGesture, 686 | overswipe: BaseOverswipeConfig, 687 | edgeHitWidth: null, 688 | isDetachable: true, 689 | }, 690 | }, 691 | animationInterpolators: { 692 | into: buildStyleInterpolator(directionMapping.FromTheEnd), 693 | out: buildStyleInterpolator(directionMapping.ToTheStart), 694 | }, 695 | }, 696 | HorizontalSwipeJumpFromRight: { 697 | ...BaseConfig, 698 | gestures: { 699 | jumpBack: { 700 | ...directionMapping.BaseEndToStartGesture, 701 | overswipe: BaseOverswipeConfig, 702 | edgeHitWidth: null, 703 | isDetachable: true, 704 | }, 705 | jumpForward: { 706 | ...directionMapping.BaseStartToEndGesture, 707 | overswipe: BaseOverswipeConfig, 708 | edgeHitWidth: null, 709 | isDetachable: true, 710 | }, 711 | pop: directionMapping.BaseEndToStartGesture, 712 | }, 713 | animationInterpolators: { 714 | into: buildStyleInterpolator(directionMapping.FromTheStart), 715 | out: buildStyleInterpolator(directionMapping.FadeToTheEnd), 716 | }, 717 | }, 718 | HorizontalSwipeJumpFromLeft: { 719 | ...BaseConfig, 720 | gestures: { 721 | jumpBack: { 722 | ...directionMapping.BaseEndToStartGesture, 723 | overswipe: BaseOverswipeConfig, 724 | edgeHitWidth: null, 725 | isDetachable: true, 726 | }, 727 | jumpForward: { 728 | ...directionMapping.BaseStartToEndGesture, 729 | overswipe: BaseOverswipeConfig, 730 | edgeHitWidth: null, 731 | isDetachable: true, 732 | }, 733 | pop: directionMapping.BaseEndToStartGesture, 734 | }, 735 | animationInterpolators: { 736 | into: buildStyleInterpolator(directionMapping.FromTheStart), 737 | out: buildStyleInterpolator(directionMapping.ToTheEnd), 738 | }, 739 | }, 740 | VerticalUpSwipeJump: { 741 | ...BaseConfig, 742 | gestures: { 743 | jumpBack: { 744 | ...BaseUpDownGesture, 745 | overswipe: BaseOverswipeConfig, 746 | edgeHitWidth: null, 747 | isDetachable: true, 748 | }, 749 | jumpForward: { 750 | ...BaseDownUpGesture, 751 | overswipe: BaseOverswipeConfig, 752 | edgeHitWidth: null, 753 | isDetachable: true, 754 | }, 755 | }, 756 | animationInterpolators: { 757 | into: buildStyleInterpolator(FromTheDown), 758 | out: buildStyleInterpolator(ToTheUp), 759 | }, 760 | }, 761 | VerticalDownSwipeJump: { 762 | ...BaseConfig, 763 | gestures: { 764 | jumpBack: { 765 | ...BaseDownUpGesture, 766 | overswipe: BaseOverswipeConfig, 767 | edgeHitWidth: null, 768 | isDetachable: true, 769 | }, 770 | jumpForward: { 771 | ...BaseUpDownGesture, 772 | overswipe: BaseOverswipeConfig, 773 | edgeHitWidth: null, 774 | isDetachable: true, 775 | }, 776 | }, 777 | animationInterpolators: { 778 | into: buildStyleInterpolator(FromTheTop), 779 | out: buildStyleInterpolator(ToTheDown), 780 | }, 781 | }, 782 | }; 783 | 784 | module.exports = NavigatorSceneConfigs; 785 | -------------------------------------------------------------------------------- /src/Subscribable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | import type EventEmitter from './EventEmitter'; 30 | 31 | /** 32 | * Subscribable provides a mixin for safely subscribing a component to an 33 | * eventEmitter 34 | * 35 | * This will be replaced with the observe interface that will be coming soon to 36 | * React Core 37 | */ 38 | 39 | var Subscribable = {}; 40 | 41 | Subscribable.Mixin = { 42 | 43 | componentWillMount: function() { 44 | this._subscribableSubscriptions = []; 45 | }, 46 | 47 | componentWillUnmount: function() { 48 | this._subscribableSubscriptions.forEach( 49 | (subscription) => subscription.remove() 50 | ); 51 | this._subscribableSubscriptions = null; 52 | }, 53 | 54 | /** 55 | * Special form of calling `addListener` that *guarantees* that a 56 | * subscription *must* be tied to a component instance, and therefore will 57 | * be cleaned up when the component is unmounted. It is impossible to create 58 | * the subscription and pass it in - this method must be the one to create 59 | * the subscription and therefore can guarantee it is retained in a way that 60 | * will be cleaned up. 61 | * 62 | * @param {EventEmitter} eventEmitter emitter to subscribe to. 63 | * @param {string} eventType Type of event to listen to. 64 | * @param {function} listener Function to invoke when event occurs. 65 | * @param {object} context Object to use as listener context. 66 | */ 67 | addListenerOn: function( 68 | eventEmitter: EventEmitter, 69 | eventType: string, 70 | listener: Function, 71 | context: Object 72 | ) { 73 | this._subscribableSubscriptions.push( 74 | eventEmitter.addListener(eventType, listener, context) 75 | ); 76 | } 77 | }; 78 | 79 | module.exports = Subscribable; 80 | -------------------------------------------------------------------------------- /src/__tests__/NavigationContext-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 'use strict'; 26 | 27 | jest 28 | .disableAutomock() 29 | .mock('ErrorUtils'); 30 | 31 | var NavigationContext = require('NavigationContext'); 32 | var NavigationEvent = require('NavigationEvent'); 33 | 34 | describe('NavigationContext', () => { 35 | it('defaults `currentRoute` to null', () => { 36 | var context = new NavigationContext(); 37 | expect(context.currentRoute).toEqual(null); 38 | }); 39 | 40 | it('updates `currentRoute`', () => { 41 | var context = new NavigationContext(); 42 | context.emit('didfocus', {route: {name: 'a'}}); 43 | expect(context.currentRoute.name).toEqual('a'); 44 | }); 45 | 46 | it('has parent', () => { 47 | var parent = new NavigationContext(); 48 | var child = new NavigationContext(); 49 | parent.appendChild(child); 50 | expect(child.parent).toBe(parent); 51 | }); 52 | 53 | it('has `top`', () => { 54 | var top = new NavigationContext(); 55 | var parent = new NavigationContext(); 56 | var child = new NavigationContext(); 57 | top.appendChild(parent); 58 | parent.appendChild(child); 59 | expect(child.top).toBe(top); 60 | }); 61 | 62 | it('captures event', () => { 63 | var parent = new NavigationContext(); 64 | var child = new NavigationContext(); 65 | parent.appendChild(child); 66 | 67 | var logs = []; 68 | 69 | var listener = (event) => { 70 | var {currentTarget, eventPhase, target, type} = event; 71 | logs.push({ 72 | currentTarget, 73 | eventPhase, 74 | target, 75 | type, 76 | }); 77 | }; 78 | 79 | parent.addListener('yo', listener, true); 80 | child.addListener('yo', listener, true); 81 | 82 | child.emit('yo'); 83 | 84 | expect(logs).toEqual([ 85 | { 86 | currentTarget: parent, 87 | eventPhase: NavigationEvent.CAPTURING_PHASE, 88 | target: child, 89 | type: 'yo', 90 | }, 91 | { 92 | currentTarget: child, 93 | eventPhase: NavigationEvent.AT_TARGET, 94 | target: child, 95 | type: 'yo', 96 | } 97 | ]); 98 | }); 99 | 100 | it('bubbles events', () => { 101 | var parent = new NavigationContext(); 102 | var child = new NavigationContext(); 103 | parent.appendChild(child); 104 | 105 | var logs = []; 106 | 107 | var listener = (event) => { 108 | var {currentTarget, eventPhase, target, type} = event; 109 | logs.push({ 110 | currentTarget, 111 | eventPhase, 112 | target, 113 | type, 114 | }); 115 | }; 116 | 117 | parent.addListener('yo', listener); 118 | child.addListener('yo', listener); 119 | 120 | child.emit('yo'); 121 | 122 | expect(logs).toEqual([ 123 | { 124 | currentTarget: child, 125 | eventPhase: NavigationEvent.AT_TARGET, 126 | target: child, 127 | type: 'yo', 128 | }, 129 | { 130 | currentTarget: parent, 131 | eventPhase: NavigationEvent.BUBBLING_PHASE, 132 | target: child, 133 | type: 'yo', 134 | }, 135 | ]); 136 | }); 137 | 138 | it('stops event propagation at capture phase', () => { 139 | var parent = new NavigationContext(); 140 | var child = new NavigationContext(); 141 | parent.appendChild(child); 142 | 143 | var counter = 0; 144 | 145 | parent.addListener('yo', event => event.stopPropagation(), true); 146 | child.addListener('yo', event => counter++, true); 147 | 148 | child.emit('yo'); 149 | 150 | expect(counter).toBe(0); 151 | }); 152 | 153 | it('stops event propagation at bubbling phase', () => { 154 | var parent = new NavigationContext(); 155 | var child = new NavigationContext(); 156 | parent.appendChild(child); 157 | 158 | var counter = 0; 159 | 160 | parent.addListener('yo', event => counter++); 161 | child.addListener('yo', event => event.stopPropagation()); 162 | 163 | child.emit('yo'); 164 | 165 | expect(counter).toBe(0); 166 | }); 167 | 168 | it('prevents event at capture phase', () => { 169 | var parent = new NavigationContext(); 170 | var child = new NavigationContext(); 171 | parent.appendChild(child); 172 | 173 | var val; 174 | parent.addListener('yo', event => event.preventDefault(), true); 175 | child.addListener('yo', event => val = event.defaultPrevented, true); 176 | 177 | child.emit('yo'); 178 | 179 | expect(val).toBe(true); 180 | }); 181 | 182 | it('prevents event at bubble phase', () => { 183 | var parent = new NavigationContext(); 184 | var child = new NavigationContext(); 185 | parent.appendChild(child); 186 | 187 | var val; 188 | parent.addListener('yo', event => val = event.defaultPrevented); 189 | child.addListener('yo', event => event.preventDefault()); 190 | 191 | child.emit('yo'); 192 | 193 | expect(val).toBe(true); 194 | }); 195 | 196 | it('emits nested events in order at capture phase', () => { 197 | var parent = new NavigationContext(); 198 | var child = new NavigationContext(); 199 | parent.appendChild(child); 200 | 201 | var logs = []; 202 | 203 | var listener = (event) => { 204 | var {currentTarget, type} = event; 205 | logs.push({ 206 | currentTarget, 207 | type, 208 | }); 209 | }; 210 | 211 | child.addListener('yo', event => { 212 | // event `didyo` should be fired after the full propagation cycle of the 213 | // `yo` event. 214 | child.emit('didyo'); 215 | }); 216 | 217 | parent.addListener('yo', listener, true); 218 | parent.addListener('didyo', listener, true); 219 | child.addListener('yo', listener, true); 220 | 221 | child.emit('yo'); 222 | 223 | expect(logs).toEqual([ 224 | {type: 'yo', currentTarget: parent}, 225 | {type: 'yo', currentTarget: child}, 226 | {type: 'didyo', currentTarget: parent}, 227 | ]); 228 | }); 229 | 230 | it('emits nested events in order at bubbling phase', () => { 231 | var parent = new NavigationContext(); 232 | var child = new NavigationContext(); 233 | parent.appendChild(child); 234 | 235 | var logs = []; 236 | 237 | var listener = (event) => { 238 | var {currentTarget, type} = event; 239 | logs.push({ 240 | currentTarget, 241 | type, 242 | }); 243 | }; 244 | 245 | child.addListener('yo', event => { 246 | // event `didyo` should be fired after the full propagation cycle of the 247 | // `yo` event. 248 | child.emit('didyo'); 249 | }); 250 | 251 | parent.addListener('yo', listener); 252 | child.addListener('yo', listener); 253 | parent.addListener('didyo', listener); 254 | 255 | child.emit('yo'); 256 | 257 | expect(logs).toEqual([ 258 | {type: 'yo', currentTarget: child}, 259 | {type: 'yo', currentTarget: parent}, 260 | {type: 'didyo', currentTarget: parent}, 261 | ]); 262 | }); 263 | }); 264 | -------------------------------------------------------------------------------- /src/__tests__/NavigationEvent-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 'use strict'; 26 | 27 | jest 28 | .unmock('NavigationEvent') 29 | .unmock('fbjs/lib/invariant'); 30 | 31 | var NavigationEvent = require('NavigationEvent'); 32 | 33 | describe('NavigationEvent', () => { 34 | it('constructs', () => { 35 | var target = {}; 36 | var event = new NavigationEvent('foo', target, 123); 37 | expect(event.type).toBe('foo'); 38 | expect(event.target).toBe(target); 39 | expect(event.data).toBe(123); 40 | }); 41 | 42 | it('constructs from pool', () => { 43 | var target = {}; 44 | var event = NavigationEvent.pool('foo', target, 123); 45 | expect(event.type).toBe('foo'); 46 | expect(event.target).toBe(target); 47 | expect(event.data).toBe(123); 48 | }); 49 | 50 | it('prevents default', () => { 51 | var event = new NavigationEvent('foo', {}, 123); 52 | expect(event.defaultPrevented).toBe(false); 53 | event.preventDefault(); 54 | expect(event.defaultPrevented).toBe(true); 55 | }); 56 | 57 | it('recycles', () => { 58 | var event1 = NavigationEvent.pool('foo', {}, 123); 59 | event1.dispose(); 60 | expect(event1.type).toBeFalsy(); 61 | expect(event1.data).toBe(null); 62 | expect(event1.target).toBe(null); 63 | 64 | var event2 = NavigationEvent.pool('bar', {}, 456); 65 | expect(event2.type).toBe('bar'); 66 | expect(event2).toBe(event1); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/__tests__/NavigationEventEmitter-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 'use strict'; 26 | 27 | jest 28 | .unmock('EmitterSubscription') 29 | .unmock('EventSubscription') 30 | .unmock('EventEmitter') 31 | .unmock('EventSubscriptionVendor') 32 | .unmock('NavigationEvent') 33 | .unmock('NavigationEventEmitter'); 34 | 35 | var NavigationEventEmitter = require('NavigationEventEmitter'); 36 | 37 | describe('NavigationEventEmitter', () => { 38 | it('emits event', () => { 39 | var context = {}; 40 | var emitter = new NavigationEventEmitter(context); 41 | var logs = []; 42 | 43 | emitter.addListener('ping', (event) => { 44 | var {type, data, target, defaultPrevented} = event; 45 | 46 | logs.push({ 47 | data, 48 | defaultPrevented, 49 | target, 50 | type, 51 | }); 52 | 53 | }); 54 | 55 | emitter.emit('ping', 'hello'); 56 | 57 | expect(logs.length).toBe(1); 58 | expect(logs[0].target).toBe(context); 59 | expect(logs[0].type).toBe('ping'); 60 | expect(logs[0].data).toBe('hello'); 61 | expect(logs[0].defaultPrevented).toBe(false); 62 | }); 63 | 64 | it('does not emit event that has no listeners', () => { 65 | var context = {}; 66 | var emitter = new NavigationEventEmitter(context); 67 | var pinged = false; 68 | 69 | emitter.addListener('ping', () => { 70 | pinged = true; 71 | }); 72 | 73 | emitter.emit('yo', 'bo'); 74 | expect(pinged).toBe(false); 75 | }); 76 | 77 | it('puts nested emit call in a queue', () => { 78 | var context = {}; 79 | var emitter = new NavigationEventEmitter(context); 80 | var logs = []; 81 | 82 | emitter.addListener('one', () => { 83 | logs.push(1); 84 | emitter.emit('two'); 85 | logs.push(2); 86 | }); 87 | 88 | emitter.addListener('two', () => { 89 | logs.push(3); 90 | emitter.emit('three'); 91 | logs.push(4); 92 | }); 93 | 94 | emitter.addListener('three', () => { 95 | logs.push(5); 96 | }); 97 | 98 | emitter.emit('one'); 99 | 100 | expect(logs).toEqual([1, 2, 3, 4, 5]); 101 | }); 102 | 103 | it('puts nested emit call in a queue should be in sequence order', () => { 104 | var context = {}; 105 | var emitter = new NavigationEventEmitter(context); 106 | var logs = []; 107 | 108 | emitter.addListener('one', () => { 109 | logs.push(1); 110 | emitter.emit('two'); 111 | emitter.emit('three'); 112 | logs.push(2); 113 | }); 114 | 115 | emitter.addListener('two', () => { 116 | logs.push(3); 117 | logs.push(4); 118 | }); 119 | 120 | emitter.addListener('three', () => { 121 | logs.push(5); 122 | }); 123 | 124 | emitter.emit('one'); 125 | 126 | expect(logs).toEqual([1, 2, 3, 4, 5]); 127 | }); 128 | 129 | it('calls callback after emitting', () => { 130 | var context = {}; 131 | var emitter = new NavigationEventEmitter(context); 132 | var logs = []; 133 | 134 | emitter.addListener('ping', (event) => { 135 | var {type, data, target, defaultPrevented} = event; 136 | logs.push({ 137 | data, 138 | defaultPrevented, 139 | target, 140 | type, 141 | }); 142 | event.preventDefault(); 143 | }); 144 | 145 | emitter.emit('ping', 'hello', (event) => { 146 | var {type, data, target, defaultPrevented} = event; 147 | logs.push({ 148 | data, 149 | defaultPrevented, 150 | target, 151 | type, 152 | }); 153 | }); 154 | 155 | expect(logs.length).toBe(2); 156 | expect(logs[1].target).toBe(context); 157 | expect(logs[1].type).toBe('ping'); 158 | expect(logs[1].data).toBe('hello'); 159 | expect(logs[1].defaultPrevented).toBe(true); 160 | }); 161 | 162 | it('calls callback after emitting the current event and before ' + 163 | 'emitting the next event', () => { 164 | var context = {}; 165 | var emitter = new NavigationEventEmitter(context); 166 | var logs = []; 167 | 168 | emitter.addListener('ping', (event) => { 169 | logs.push('ping'); 170 | emitter.emit('pong'); 171 | }); 172 | 173 | emitter.addListener('pong', (event) => { 174 | logs.push('pong'); 175 | }); 176 | 177 | emitter.emit('ping', null, () => { 178 | logs.push('did-ping'); 179 | }); 180 | 181 | expect(logs).toEqual([ 182 | 'ping', 183 | 'did-ping', 184 | 'pong', 185 | ]); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /src/__tests__/NavigationRouteStack-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 'use strict'; 26 | 27 | 28 | jest 29 | .disableAutomock() 30 | .mock('ErrorUtils'); 31 | 32 | var NavigationRouteStack = require('NavigationRouteStack'); 33 | 34 | function assetStringNotEmpty(str) { 35 | expect(!!str && typeof str === 'string').toBe(true); 36 | } 37 | 38 | describe('NavigationRouteStack:', () => { 39 | // Different types of routes. 40 | var ROUTES = [ 41 | 'foo', 42 | 1, 43 | true, 44 | {foo: 'bar'}, 45 | ['foo'], 46 | ]; 47 | 48 | // Basic 49 | it('gets index', () => { 50 | var stack = new NavigationRouteStack(1, ['a', 'b', 'c']); 51 | expect(stack.index).toBe(1); 52 | }); 53 | 54 | it('gets size', () => { 55 | var stack = new NavigationRouteStack(1, ['a', 'b', 'c']); 56 | expect(stack.size).toBe(3); 57 | }); 58 | 59 | it('gets route', () => { 60 | var stack = new NavigationRouteStack(0, ['a', 'b', 'c']); 61 | expect(stack.get(2)).toBe('c'); 62 | }); 63 | 64 | it('converts to an array', () => { 65 | var stack = new NavigationRouteStack(0, ['a', 'b']); 66 | expect(stack.toArray()).toEqual(['a', 'b']); 67 | }); 68 | 69 | it('creates a new stack after mutation', () => { 70 | var stack1 = new NavigationRouteStack(0, ['a', 'b']); 71 | var stack2 = stack1.push('c'); 72 | expect(stack1).not.toBe(stack2); 73 | }); 74 | 75 | it('throws at index out of bound', () => { 76 | expect(() => { 77 | new NavigationRouteStack(-1, ['a', 'b']); 78 | }).toThrow(); 79 | 80 | expect(() => { 81 | new NavigationRouteStack(100, ['a', 'b']); 82 | }).toThrow(); 83 | }); 84 | 85 | it('finds index', () => { 86 | var stack = new NavigationRouteStack(0, ['a', 'b']); 87 | expect(stack.indexOf('b')).toBe(1); 88 | expect(stack.indexOf('c')).toBe(-1); 89 | }); 90 | 91 | // Key 92 | it('gets key for route', () => { 93 | var test = (route) => { 94 | var stack = new NavigationRouteStack(0, ['a']); 95 | var key = stack.push(route).keyOf(route); 96 | expect(typeof key).toBe('string'); 97 | expect(!!key).toBe(true); 98 | }; 99 | 100 | ROUTES.forEach(test); 101 | }); 102 | 103 | it('gets a key of larger value for route', () => { 104 | var lastKey = ''; 105 | var test = (route) => { 106 | var stack = new NavigationRouteStack(0, ['a']); 107 | var key = stack.push(route).keyOf(route); 108 | expect(key > lastKey).toBe(true); 109 | lastKey = key; 110 | }; 111 | 112 | ROUTES.forEach(test); 113 | }); 114 | 115 | it('gets an unique key for a different route', () => { 116 | var stack = new NavigationRouteStack(0, ['a']); 117 | var keys = {}; 118 | 119 | var test = (route) => { 120 | stack = stack.push(route); 121 | var key = stack.keyOf(route); 122 | expect(keys[key]).toBe(undefined); 123 | keys[key] = true; 124 | }; 125 | 126 | ROUTES.forEach(test); 127 | }); 128 | 129 | it('gets the same unique key for the same route', () => { 130 | var test = (route) => { 131 | var stack = new NavigationRouteStack(0, [route]); 132 | expect(stack.keyOf(route)).toBe(stack.keyOf(route)); 133 | }; 134 | 135 | ROUTES.forEach(test); 136 | }); 137 | 138 | 139 | it('gets the same unique key form the derived stack', () => { 140 | var test = (route) => { 141 | var stack = new NavigationRouteStack(0, [route]); 142 | var derivedStack = stack.push('wow').pop().slice(0, 10).push('blah'); 143 | expect(derivedStack.keyOf(route)).toBe(stack.keyOf(route)); 144 | }; 145 | 146 | ROUTES.forEach(test); 147 | }); 148 | 149 | it('gets a different key from a different stack', () => { 150 | var test = (route) => { 151 | var stack1 = new NavigationRouteStack(0, [route]); 152 | var stack2 = new NavigationRouteStack(0, [route]); 153 | expect(stack1.keyOf(route)).not.toBe(stack2.keyOf(route)); 154 | }; 155 | 156 | ROUTES.forEach(test); 157 | }); 158 | 159 | it('gets no key for a route that does not contains this route', () => { 160 | var stack = new NavigationRouteStack(0, ['a']); 161 | expect(stack.keyOf('b')).toBe(null); 162 | }); 163 | 164 | it('gets a new key for a route that was removed and added again', () => { 165 | var test = (route) => { 166 | var stack = new NavigationRouteStack(0, ['a']); 167 | 168 | var key1 = stack.push(route).keyOf(route); 169 | var key2 = stack.push(route).pop().push(route).keyOf(route); 170 | expect(key1).not.toBe(key2); 171 | }; 172 | 173 | ROUTES.forEach(test); 174 | }); 175 | 176 | // Slice 177 | it('slices', () => { 178 | var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']); 179 | var stack2 = stack1.slice(1, 3); 180 | expect(stack2).not.toBe(stack1); 181 | expect(stack2.toArray()).toEqual(['b', 'c']); 182 | }); 183 | 184 | it('may update index after slicing', () => { 185 | var stack = new NavigationRouteStack(2, ['a', 'b', 'c']); 186 | expect(stack.slice().index).toBe(2); 187 | expect(stack.slice(0, 1).index).toBe(0); 188 | expect(stack.slice(0, 2).index).toBe(1); 189 | expect(stack.slice(0, 3).index).toBe(2); 190 | expect(stack.slice(0, 100).index).toBe(2); 191 | expect(stack.slice(-2).index).toBe(1); 192 | }); 193 | 194 | it('slices without specifying params', () => { 195 | var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']); 196 | var stack2 = stack1.slice(); 197 | expect(stack2).toBe(stack1); 198 | }); 199 | 200 | it('slices to from the end', () => { 201 | var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']); 202 | var stack2 = stack1.slice(-2); 203 | expect(stack2.toArray()).toEqual(['c', 'd']); 204 | }); 205 | 206 | it('throws when slicing to empty', () => { 207 | expect(() => { 208 | var stack = new NavigationRouteStack(1, ['a', 'b']); 209 | stack.slice(100); 210 | }).toThrow(); 211 | }); 212 | 213 | // Push 214 | it('pushes route', () => { 215 | var stack1 = new NavigationRouteStack(1, ['a', 'b']); 216 | var stack2 = stack1.push('c'); 217 | 218 | expect(stack2).not.toBe(stack1); 219 | expect(stack2.toArray()).toEqual(['a', 'b', 'c']); 220 | expect(stack2.index).toBe(2); 221 | expect(stack2.size).toBe(3); 222 | }); 223 | 224 | it('throws when pushing empty route', () => { 225 | expect(() => { 226 | var stack = new NavigationRouteStack(1, ['a', 'b']); 227 | stack.push(null); 228 | }).toThrow(); 229 | 230 | expect(() => { 231 | var stack = new NavigationRouteStack(1, ['a', 'b']); 232 | stack.push(''); 233 | }).toThrow(); 234 | 235 | expect(() => { 236 | var stack = new NavigationRouteStack(1, ['a', 'b']); 237 | stack.push(undefined); 238 | }).toThrow(); 239 | }); 240 | 241 | it('replaces routes on push', () => { 242 | var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']); 243 | var stack2 = stack1.push('d'); 244 | expect(stack2).not.toBe(stack1); 245 | expect(stack2.toArray()).toEqual(['a', 'b', 'd']); 246 | expect(stack2.index).toBe(2); 247 | }); 248 | 249 | // Pop 250 | it('pops route', () => { 251 | var stack1 = new NavigationRouteStack(2, ['a', 'b', 'c']); 252 | var stack2 = stack1.pop(); 253 | expect(stack2).not.toBe(stack1); 254 | expect(stack2.toArray()).toEqual(['a', 'b']); 255 | expect(stack2.index).toBe(1); 256 | expect(stack2.size).toBe(2); 257 | }); 258 | 259 | it('replaces routes on pop', () => { 260 | var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']); 261 | var stack2 = stack1.pop(); 262 | expect(stack2).not.toBe(stack1); 263 | expect(stack2.toArray()).toEqual(['a']); 264 | expect(stack2.index).toBe(0); 265 | }); 266 | 267 | it('throws when popping to empty stack', () => { 268 | expect(() => { 269 | var stack = new NavigationRouteStack(0, ['a']); 270 | stack.pop(); 271 | }).toThrow(); 272 | }); 273 | 274 | // Jump 275 | it('jumps to index', () => { 276 | var stack1 = new NavigationRouteStack(0, ['a', 'b', 'c']); 277 | var stack2 = stack1.jumpToIndex(2); 278 | 279 | expect(stack2).not.toBe(stack1); 280 | expect(stack2.index).toBe(2); 281 | }); 282 | 283 | it('throws then jumping to index out of bound', () => { 284 | expect(() => { 285 | var stack = new NavigationRouteStack(1, ['a', 'b']); 286 | stack.jumpToIndex(2); 287 | }).toThrow(); 288 | 289 | expect(() => { 290 | var stack = new NavigationRouteStack(1, ['a', 'b']); 291 | stack.jumpToIndex(-1); 292 | }).toThrow(); 293 | }); 294 | 295 | // Replace 296 | it('replaces route at index', () => { 297 | var stack1 = new NavigationRouteStack(1, ['a', 'b']); 298 | var stack2 = stack1.replaceAtIndex(0, 'x'); 299 | 300 | expect(stack2).not.toBe(stack1); 301 | expect(stack2.toArray()).toEqual(['x', 'b']); 302 | expect(stack2.index).toBe(0); 303 | }); 304 | 305 | it('replaces route at negative index', () => { 306 | var stack1 = new NavigationRouteStack(1, ['a', 'b']); 307 | var stack2 = stack1.replaceAtIndex(-1, 'x'); 308 | 309 | expect(stack2).not.toBe(stack1); 310 | expect(stack2.toArray()).toEqual(['a', 'x']); 311 | expect(stack2.index).toBe(1); 312 | }); 313 | 314 | it('throws when replacing empty route', () => { 315 | expect(() => { 316 | var stack = new NavigationRouteStack(1, ['a', 'b']); 317 | stack.replaceAtIndex(1, null); 318 | }).toThrow(); 319 | }); 320 | 321 | it('throws when replacing at index out of bound', () => { 322 | expect(() => { 323 | var stack = new NavigationRouteStack(1, ['a', 'b']); 324 | stack.replaceAtIndex(100, 'x'); 325 | }).toThrow(); 326 | }); 327 | 328 | // Iteration 329 | it('iterates each item', () => { 330 | var stack = new NavigationRouteStack(0, ['a', 'b']); 331 | var logs = []; 332 | var keys = {}; 333 | var context = {name: 'yo'}; 334 | 335 | stack.forEach(function (route, index, key) { 336 | assetStringNotEmpty(key); 337 | if (!keys.hasOwnProperty(key)) { 338 | keys[key] = true; 339 | logs.push([ 340 | route, 341 | index, 342 | this.name, 343 | ]); 344 | } 345 | }, context); 346 | 347 | expect(logs).toEqual([ 348 | ['a', 0, 'yo'], 349 | ['b', 1, 'yo'], 350 | ]); 351 | }); 352 | 353 | it('Maps to an array', () => { 354 | var stack = new NavigationRouteStack(0, ['a', 'b']); 355 | var keys = {}; 356 | var context = {name: 'yo'}; 357 | 358 | var logs = stack.mapToArray(function(route, index, key) { 359 | assetStringNotEmpty(key); 360 | if (!keys.hasOwnProperty(key)) { 361 | keys[key] = true; 362 | return [ 363 | route, 364 | index, 365 | this.name, 366 | ]; 367 | } 368 | }, context); 369 | 370 | expect(logs).toEqual([ 371 | ['a', 0, 'yo'], 372 | ['b', 1, 'yo'], 373 | ]); 374 | }); 375 | 376 | // Diff 377 | it('subtracts stack', () => { 378 | var stack1 = new NavigationRouteStack(2, ['a', 'b', 'c']); 379 | var stack2 = stack1.pop().pop().push('x').push('y'); 380 | 381 | var diff = stack1.subtract(stack2); 382 | 383 | var result = diff.toJS().map((record) => { 384 | assetStringNotEmpty(record.key); 385 | return { 386 | index: record.index, 387 | route: record.route, 388 | }; 389 | }); 390 | 391 | // route `b` and `c` are no longer in the stack. 392 | expect(result).toEqual([ 393 | { 394 | index: 1, 395 | route: 'b', 396 | }, 397 | { 398 | index: 2, 399 | route: 'c', 400 | }, 401 | ]); 402 | }); 403 | 404 | it('only subtracts the derived stack', () => { 405 | var stack1 = new NavigationRouteStack(2, ['a', 'b', 'c']); 406 | var stack2 = new NavigationRouteStack(0, ['a']); 407 | var diff = stack1.subtract(stack2); 408 | 409 | var result = diff.toJS().map((record) => { 410 | assetStringNotEmpty(record.key); 411 | return { 412 | index: record.index, 413 | route: record.route, 414 | }; 415 | }); 416 | 417 | expect(result).toEqual([ 418 | { 419 | index: 0, 420 | route: 'a', 421 | }, 422 | { 423 | index: 1, 424 | route: 'b', 425 | }, 426 | { 427 | index: 2, 428 | route: 'c', 429 | }, 430 | ]); 431 | 432 | }); 433 | }); 434 | -------------------------------------------------------------------------------- /src/__tests__/NavigationTreeNode-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | jest 8 | .unmock('NavigationTreeNode') 9 | .unmock('fbjs/lib/invariant') 10 | .unmock('immutable'); 11 | 12 | var NavigationTreeNode = require('NavigationTreeNode'); 13 | 14 | describe('NavigationTreeNode-test', () => { 15 | it('should be empty', () => { 16 | var node = new NavigationTreeNode(); 17 | expect(node.getValue()).toEqual(undefined); 18 | expect(node.getParent()).toEqual(null); 19 | expect(node.getChildrenCount()).toEqual(0); 20 | expect(node.getChildAt(0)).toEqual(null); 21 | }); 22 | 23 | 24 | it('should contain value', () => { 25 | var node = new NavigationTreeNode(123); 26 | expect(node.getValue()).toEqual(123); 27 | }); 28 | 29 | it('should appendChild', () => { 30 | var papa = new NavigationTreeNode('hedger'); 31 | var baby = new NavigationTreeNode('hedger jr'); 32 | papa.appendChild(baby); 33 | expect(papa.getChildAt(0)).toEqual(baby); 34 | expect(papa.getChildrenCount()).toEqual(1); 35 | expect(baby.getParent()).toEqual(papa); 36 | }); 37 | 38 | it('should removeChild', () => { 39 | var papa = new NavigationTreeNode('Eddard Stark'); 40 | var baby = new NavigationTreeNode('Robb Stark'); 41 | papa.appendChild(baby); 42 | 43 | papa.removeChild(baby); 44 | expect(papa.getChildAt(0)).toEqual(null); 45 | expect(papa.getChildrenCount()).toEqual(0); 46 | expect(baby.getParent()).toEqual(null); 47 | }); 48 | 49 | it('should not remove non-child', () => { 50 | var papa = new NavigationTreeNode('dog'); 51 | var baby = new NavigationTreeNode('cat'); 52 | expect(papa.removeChild.bind(papa, baby)).toThrow(); 53 | }); 54 | 55 | it('should find child', () => { 56 | var papa = new NavigationTreeNode('Eddard Stark'); 57 | var baby = new NavigationTreeNode('Robb Stark'); 58 | 59 | papa.appendChild(baby); 60 | expect(papa.indexOf(baby)).toEqual(0); 61 | 62 | papa.removeChild(baby); 63 | expect(papa.indexOf(baby)).toEqual(-1); 64 | }); 65 | 66 | 67 | it('should traverse each child', () => { 68 | var parent = new NavigationTreeNode(); 69 | parent.appendChild(new NavigationTreeNode('a')); 70 | parent.appendChild(new NavigationTreeNode('b')); 71 | parent.appendChild(new NavigationTreeNode('c')); 72 | var result = []; 73 | parent.forEach((child, index) => { 74 | result[index] = child.getValue(); 75 | }); 76 | 77 | expect(result).toEqual(['a', 'b', 'c']); 78 | }); 79 | 80 | it('should map children', () => { 81 | var parent = new NavigationTreeNode(); 82 | parent.appendChild(new NavigationTreeNode('a')); 83 | parent.appendChild(new NavigationTreeNode('b')); 84 | parent.appendChild(new NavigationTreeNode('c')); 85 | var result = parent.map((child, index) => { 86 | return child.getValue(); 87 | }); 88 | 89 | expect(result).toEqual(['a', 'b', 'c']); 90 | }); 91 | 92 | it('should traverse some children', () => { 93 | var parent = new NavigationTreeNode(); 94 | parent.appendChild(new NavigationTreeNode('a')); 95 | parent.appendChild(new NavigationTreeNode('b')); 96 | parent.appendChild(new NavigationTreeNode('c')); 97 | 98 | var result = []; 99 | var value = parent.some((child, index) => { 100 | if (index > 1) { 101 | return true; 102 | } else { 103 | result[index] = child.getValue(); 104 | } 105 | }); 106 | 107 | expect(value).toEqual(true); 108 | expect(result).toEqual(['a', 'b']); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /src/buildStyleInterpolator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 27 | /** 28 | * Cannot "use strict" because we must use eval in this file. 29 | */ 30 | /* eslint-disable global-strict */ 31 | 32 | var keyOf = require('fbjs/lib/keyOf'); 33 | 34 | var X_DIM = keyOf({x: null}); 35 | var Y_DIM = keyOf({y: null}); 36 | var Z_DIM = keyOf({z: null}); 37 | var W_DIM = keyOf({w: null}); 38 | 39 | var TRANSFORM_ROTATE_NAME = keyOf({transformRotateRadians: null}); 40 | 41 | var ShouldAllocateReusableOperationVars = { 42 | transformRotateRadians: true, 43 | transformScale: true, 44 | transformTranslate: true, 45 | }; 46 | 47 | var InitialOperationField = { 48 | transformRotateRadians: [0, 0, 0, 1], 49 | transformTranslate: [0, 0, 0], 50 | transformScale: [1, 1, 1], 51 | }; 52 | 53 | 54 | /** 55 | * Creates a highly specialized animation function that may be evaluated every 56 | * frame. For example: 57 | * 58 | * var ToTheLeft = { 59 | * opacity: { 60 | * from: 1, 61 | * to: 0.7, 62 | * min: 0, 63 | * max: 1, 64 | * type: 'linear', 65 | * extrapolate: false, 66 | * round: 100, 67 | * }, 68 | * left: { 69 | * from: 0, 70 | * to: -SCREEN_WIDTH * 0.3, 71 | * min: 0, 72 | * max: 1, 73 | * type: 'linear', 74 | * extrapolate: true, 75 | * round: PixelRatio.get(), 76 | * }, 77 | * }; 78 | * 79 | * var toTheLeft = buildStyleInterpolator(ToTheLeft); 80 | * 81 | * Would returns a specialized function of the form: 82 | * 83 | * function(result, value) { 84 | * var didChange = false; 85 | * var nextScalarVal; 86 | * var ratio; 87 | * ratio = (value - 0) / 1; 88 | * ratio = ratio > 1 ? 1 : (ratio < 0 ? 0 : ratio); 89 | * nextScalarVal = Math.round(100 * (1 * (1 - ratio) + 0.7 * ratio)) / 100; 90 | * if (!didChange) { 91 | * var prevVal = result.opacity; 92 | * result.opacity = nextScalarVal; 93 | * didChange = didChange || (nextScalarVal !== prevVal); 94 | * } else { 95 | * result.opacity = nextScalarVal; 96 | * } 97 | * ratio = (value - 0) / 1; 98 | * nextScalarVal = Math.round(2 * (0 * (1 - ratio) + -30 * ratio)) / 2; 99 | * if (!didChange) { 100 | * var prevVal = result.left; 101 | * result.left = nextScalarVal; 102 | * didChange = didChange || (nextScalarVal !== prevVal); 103 | * } else { 104 | * result.left = nextScalarVal; 105 | * } 106 | * return didChange; 107 | * } 108 | */ 109 | 110 | var ARGUMENT_NAMES_RE = /([^\s,]+)/g; 111 | /** 112 | * This is obviously a huge hack. Proper tooling would allow actual inlining. 113 | * This only works in a few limited cases (where there is no function return 114 | * value, and the function operates mutatively on parameters). 115 | * 116 | * Example: 117 | * 118 | * 119 | * var inlineMe(a, b) { 120 | * a = b + b; 121 | * }; 122 | * 123 | * inline(inlineMe, ['hi', 'bye']); // "hi = bye + bye;" 124 | * 125 | * @param {string} fnStr Source of any simple function who's arguments can be 126 | * replaced via a regex. 127 | * @param {array} replaceWithArgs Corresponding names of variables 128 | * within an environment, to replace `func` args with. 129 | * @return {string} Resulting function body string. 130 | */ 131 | var inline = function(fnStr, replaceWithArgs) { 132 | var parameterNames = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')) 133 | .match(ARGUMENT_NAMES_RE) || 134 | []; 135 | var replaceRegexStr = parameterNames.map(function(paramName) { 136 | return '\\b' + paramName + '\\b'; 137 | }).join('|'); 138 | var replaceRegex = new RegExp(replaceRegexStr, 'g'); 139 | var fnBody = fnStr.substring(fnStr.indexOf('{') + 1, fnStr.lastIndexOf('}')); 140 | var newFnBody = fnBody.replace(replaceRegex, function(parameterName) { 141 | var indexInParameterNames = parameterNames.indexOf(parameterName); 142 | var replacementName = replaceWithArgs[indexInParameterNames]; 143 | return replacementName; 144 | }); 145 | return newFnBody.split('\n'); 146 | }; 147 | 148 | /** 149 | * Simply a convenient way to inline functions using the inline function. 150 | */ 151 | var MatrixOps = { 152 | unroll: `function(matVar, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15) { 153 | m0 = matVar[0]; 154 | m1 = matVar[1]; 155 | m2 = matVar[2]; 156 | m3 = matVar[3]; 157 | m4 = matVar[4]; 158 | m5 = matVar[5]; 159 | m6 = matVar[6]; 160 | m7 = matVar[7]; 161 | m8 = matVar[8]; 162 | m9 = matVar[9]; 163 | m10 = matVar[10]; 164 | m11 = matVar[11]; 165 | m12 = matVar[12]; 166 | m13 = matVar[13]; 167 | m14 = matVar[14]; 168 | m15 = matVar[15]; 169 | }`, 170 | 171 | matrixDiffers: `function(retVar, matVar, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15) { 172 | retVar = retVar || 173 | m0 !== matVar[0] || 174 | m1 !== matVar[1] || 175 | m2 !== matVar[2] || 176 | m3 !== matVar[3] || 177 | m4 !== matVar[4] || 178 | m5 !== matVar[5] || 179 | m6 !== matVar[6] || 180 | m7 !== matVar[7] || 181 | m8 !== matVar[8] || 182 | m9 !== matVar[9] || 183 | m10 !== matVar[10] || 184 | m11 !== matVar[11] || 185 | m12 !== matVar[12] || 186 | m13 !== matVar[13] || 187 | m14 !== matVar[14] || 188 | m15 !== matVar[15]; 189 | }`, 190 | 191 | transformScale: `function(matVar, opVar) { 192 | // Scaling matVar by opVar 193 | var x = opVar[0]; 194 | var y = opVar[1]; 195 | var z = opVar[2]; 196 | matVar[0] = matVar[0] * x; 197 | matVar[1] = matVar[1] * x; 198 | matVar[2] = matVar[2] * x; 199 | matVar[3] = matVar[3] * x; 200 | matVar[4] = matVar[4] * y; 201 | matVar[5] = matVar[5] * y; 202 | matVar[6] = matVar[6] * y; 203 | matVar[7] = matVar[7] * y; 204 | matVar[8] = matVar[8] * z; 205 | matVar[9] = matVar[9] * z; 206 | matVar[10] = matVar[10] * z; 207 | matVar[11] = matVar[11] * z; 208 | matVar[12] = matVar[12]; 209 | matVar[13] = matVar[13]; 210 | matVar[14] = matVar[14]; 211 | matVar[15] = matVar[15]; 212 | }`, 213 | 214 | /** 215 | * All of these matrix transforms are not general purpose utilities, and are 216 | * only suitable for being inlined for the use of building up interpolators. 217 | */ 218 | transformTranslate: `function(matVar, opVar) { 219 | // Translating matVar by opVar 220 | var x = opVar[0]; 221 | var y = opVar[1]; 222 | var z = opVar[2]; 223 | matVar[12] = matVar[0] * x + matVar[4] * y + matVar[8] * z + matVar[12]; 224 | matVar[13] = matVar[1] * x + matVar[5] * y + matVar[9] * z + matVar[13]; 225 | matVar[14] = matVar[2] * x + matVar[6] * y + matVar[10] * z + matVar[14]; 226 | matVar[15] = matVar[3] * x + matVar[7] * y + matVar[11] * z + matVar[15]; 227 | }`, 228 | 229 | /** 230 | * @param {array} matVar Both the input, and the output matrix. 231 | * @param {quaternion specification} q Four element array describing rotation. 232 | */ 233 | transformRotateRadians: `function(matVar, q) { 234 | // Rotating matVar by q 235 | var xQuat = q[0], yQuat = q[1], zQuat = q[2], wQuat = q[3]; 236 | var x2Quat = xQuat + xQuat; 237 | var y2Quat = yQuat + yQuat; 238 | var z2Quat = zQuat + zQuat; 239 | var xxQuat = xQuat * x2Quat; 240 | var xyQuat = xQuat * y2Quat; 241 | var xzQuat = xQuat * z2Quat; 242 | var yyQuat = yQuat * y2Quat; 243 | var yzQuat = yQuat * z2Quat; 244 | var zzQuat = zQuat * z2Quat; 245 | var wxQuat = wQuat * x2Quat; 246 | var wyQuat = wQuat * y2Quat; 247 | var wzQuat = wQuat * z2Quat; 248 | // Step 1: Inlines the construction of a quaternion matrix ('quatMat') 249 | var quatMat0 = 1 - (yyQuat + zzQuat); 250 | var quatMat1 = xyQuat + wzQuat; 251 | var quatMat2 = xzQuat - wyQuat; 252 | var quatMat4 = xyQuat - wzQuat; 253 | var quatMat5 = 1 - (xxQuat + zzQuat); 254 | var quatMat6 = yzQuat + wxQuat; 255 | var quatMat8 = xzQuat + wyQuat; 256 | var quatMat9 = yzQuat - wxQuat; 257 | var quatMat10 = 1 - (xxQuat + yyQuat); 258 | // quatMat3/7/11/12/13/14 = 0, quatMat15 = 1 259 | 260 | // Step 2: Inlines multiplication, takes advantage of constant quatMat cells 261 | var a00 = matVar[0]; 262 | var a01 = matVar[1]; 263 | var a02 = matVar[2]; 264 | var a03 = matVar[3]; 265 | var a10 = matVar[4]; 266 | var a11 = matVar[5]; 267 | var a12 = matVar[6]; 268 | var a13 = matVar[7]; 269 | var a20 = matVar[8]; 270 | var a21 = matVar[9]; 271 | var a22 = matVar[10]; 272 | var a23 = matVar[11]; 273 | 274 | var b0 = quatMat0, b1 = quatMat1, b2 = quatMat2; 275 | matVar[0] = b0 * a00 + b1 * a10 + b2 * a20; 276 | matVar[1] = b0 * a01 + b1 * a11 + b2 * a21; 277 | matVar[2] = b0 * a02 + b1 * a12 + b2 * a22; 278 | matVar[3] = b0 * a03 + b1 * a13 + b2 * a23; 279 | b0 = quatMat4; b1 = quatMat5; b2 = quatMat6; 280 | matVar[4] = b0 * a00 + b1 * a10 + b2 * a20; 281 | matVar[5] = b0 * a01 + b1 * a11 + b2 * a21; 282 | matVar[6] = b0 * a02 + b1 * a12 + b2 * a22; 283 | matVar[7] = b0 * a03 + b1 * a13 + b2 * a23; 284 | b0 = quatMat8; b1 = quatMat9; b2 = quatMat10; 285 | matVar[8] = b0 * a00 + b1 * a10 + b2 * a20; 286 | matVar[9] = b0 * a01 + b1 * a11 + b2 * a21; 287 | matVar[10] = b0 * a02 + b1 * a12 + b2 * a22; 288 | matVar[11] = b0 * a03 + b1 * a13 + b2 * a23; 289 | }` 290 | }; 291 | 292 | // Optimized version of general operation applications that can be used when 293 | // the target matrix is known to be the identity matrix. 294 | var MatrixOpsInitial = { 295 | transformScale: `function(matVar, opVar) { 296 | // Scaling matVar known to be identity by opVar 297 | matVar[0] = opVar[0]; 298 | matVar[1] = 0; 299 | matVar[2] = 0; 300 | matVar[3] = 0; 301 | matVar[4] = 0; 302 | matVar[5] = opVar[1]; 303 | matVar[6] = 0; 304 | matVar[7] = 0; 305 | matVar[8] = 0; 306 | matVar[9] = 0; 307 | matVar[10] = opVar[2]; 308 | matVar[11] = 0; 309 | matVar[12] = 0; 310 | matVar[13] = 0; 311 | matVar[14] = 0; 312 | matVar[15] = 1; 313 | }`, 314 | 315 | transformTranslate: `function(matVar, opVar) { 316 | // Translating matVar known to be identity by opVar; 317 | matVar[0] = 1; 318 | matVar[1] = 0; 319 | matVar[2] = 0; 320 | matVar[3] = 0; 321 | matVar[4] = 0; 322 | matVar[5] = 1; 323 | matVar[6] = 0; 324 | matVar[7] = 0; 325 | matVar[8] = 0; 326 | matVar[9] = 0; 327 | matVar[10] = 1; 328 | matVar[11] = 0; 329 | matVar[12] = opVar[0]; 330 | matVar[13] = opVar[1]; 331 | matVar[14] = opVar[2]; 332 | matVar[15] = 1; 333 | }`, 334 | 335 | /** 336 | * @param {array} matVar Both the input, and the output matrix - assumed to be 337 | * identity. 338 | * @param {quaternion specification} q Four element array describing rotation. 339 | */ 340 | transformRotateRadians: `function(matVar, q) { 341 | 342 | // Rotating matVar which is known to be identity by q 343 | var xQuat = q[0], yQuat = q[1], zQuat = q[2], wQuat = q[3]; 344 | var x2Quat = xQuat + xQuat; 345 | var y2Quat = yQuat + yQuat; 346 | var z2Quat = zQuat + zQuat; 347 | var xxQuat = xQuat * x2Quat; 348 | var xyQuat = xQuat * y2Quat; 349 | var xzQuat = xQuat * z2Quat; 350 | var yyQuat = yQuat * y2Quat; 351 | var yzQuat = yQuat * z2Quat; 352 | var zzQuat = zQuat * z2Quat; 353 | var wxQuat = wQuat * x2Quat; 354 | var wyQuat = wQuat * y2Quat; 355 | var wzQuat = wQuat * z2Quat; 356 | // Step 1: Inlines the construction of a quaternion matrix ('quatMat') 357 | var quatMat0 = 1 - (yyQuat + zzQuat); 358 | var quatMat1 = xyQuat + wzQuat; 359 | var quatMat2 = xzQuat - wyQuat; 360 | var quatMat4 = xyQuat - wzQuat; 361 | var quatMat5 = 1 - (xxQuat + zzQuat); 362 | var quatMat6 = yzQuat + wxQuat; 363 | var quatMat8 = xzQuat + wyQuat; 364 | var quatMat9 = yzQuat - wxQuat; 365 | var quatMat10 = 1 - (xxQuat + yyQuat); 366 | // quatMat3/7/11/12/13/14 = 0, quatMat15 = 1 367 | 368 | // Step 2: Inlines the multiplication with identity matrix. 369 | var b0 = quatMat0, b1 = quatMat1, b2 = quatMat2; 370 | matVar[0] = b0; 371 | matVar[1] = b1; 372 | matVar[2] = b2; 373 | matVar[3] = 0; 374 | b0 = quatMat4; b1 = quatMat5; b2 = quatMat6; 375 | matVar[4] = b0; 376 | matVar[5] = b1; 377 | matVar[6] = b2; 378 | matVar[7] = 0; 379 | b0 = quatMat8; b1 = quatMat9; b2 = quatMat10; 380 | matVar[8] = b0; 381 | matVar[9] = b1; 382 | matVar[10] = b2; 383 | matVar[11] = 0; 384 | matVar[12] = 0; 385 | matVar[13] = 0; 386 | matVar[14] = 0; 387 | matVar[15] = 1; 388 | }` 389 | }; 390 | 391 | 392 | var setNextValAndDetectChange = function(name, tmpVarName) { 393 | return ( 394 | ' if (!didChange) {\n' + 395 | ' var prevVal = result.' + name + ';\n' + 396 | ' result.' + name + ' = ' + tmpVarName + ';\n' + 397 | ' didChange = didChange || (' + tmpVarName + ' !== prevVal);\n' + 398 | ' } else {\n' + 399 | ' result.' + name + ' = ' + tmpVarName + ';\n' + 400 | ' }\n' 401 | ); 402 | }; 403 | 404 | var computeNextValLinear = function(anim, from, to, tmpVarName) { 405 | var hasRoundRatio = 'round' in anim; 406 | var roundRatio = anim.round; 407 | var fn = ' ratio = (value - ' + anim.min + ') / ' + (anim.max - anim.min) + ';\n'; 408 | if (!anim.extrapolate) { 409 | fn += ' ratio = ratio > 1 ? 1 : (ratio < 0 ? 0 : ratio);\n'; 410 | } 411 | 412 | var roundOpen = (hasRoundRatio ? 'Math.round(' + roundRatio + ' * ' : '' ); 413 | var roundClose = (hasRoundRatio ? ') / ' + roundRatio : '' ); 414 | fn += 415 | ' ' + tmpVarName + ' = ' + 416 | roundOpen + 417 | '(' + from + ' * (1 - ratio) + ' + to + ' * ratio)' + 418 | roundClose + ';\n'; 419 | return fn; 420 | }; 421 | 422 | var computeNextValLinearScalar = function(anim) { 423 | return computeNextValLinear(anim, anim.from, anim.to, 'nextScalarVal'); 424 | }; 425 | 426 | var computeNextValConstant = function(anim) { 427 | var constantExpression = JSON.stringify(anim.value); 428 | return ' nextScalarVal = ' + constantExpression + ';\n'; 429 | }; 430 | 431 | var computeNextValStep = function(anim) { 432 | return ( 433 | ' nextScalarVal = value >= ' + 434 | (anim.threshold + ' ? ' + anim.to + ' : ' + anim.from) + ';\n' 435 | ); 436 | }; 437 | 438 | var computeNextValIdentity = function(anim) { 439 | return ' nextScalarVal = value;\n'; 440 | }; 441 | 442 | var operationVar = function(name) { 443 | return name + 'ReuseOp'; 444 | }; 445 | 446 | var createReusableOperationVars = function(anims) { 447 | var ret = ''; 448 | for (var name in anims) { 449 | if (ShouldAllocateReusableOperationVars[name]) { 450 | ret += 'var ' + operationVar(name) + ' = [];\n'; 451 | } 452 | } 453 | return ret; 454 | }; 455 | 456 | var newlines = function(statements) { 457 | return '\n' + statements.join('\n') + '\n'; 458 | }; 459 | 460 | /** 461 | * @param {Animation} anim Configuration entry. 462 | * @param {key} dimension Key to examine in `from`/`to`. 463 | * @param {number} index Field in operationVar to set. 464 | * @return {string} Code that sets the operation variable's field. 465 | */ 466 | var computeNextMatrixOperationField = function(anim, name, dimension, index) { 467 | var fieldAccess = operationVar(name) + '[' + index + ']'; 468 | if (anim.from[dimension] !== undefined && anim.to[dimension] !== undefined) { 469 | return ' ' + anim.from[dimension] !== anim.to[dimension] ? 470 | computeNextValLinear(anim, anim.from[dimension], anim.to[dimension], fieldAccess) : 471 | fieldAccess + ' = ' + anim.from[dimension] + ';'; 472 | } else { 473 | return ' ' + fieldAccess + ' = ' + InitialOperationField[name][index] + ';'; 474 | } 475 | }; 476 | 477 | var unrolledVars = []; 478 | for (var varIndex = 0; varIndex < 16; varIndex++) { 479 | unrolledVars.push('m' + varIndex); 480 | } 481 | var setNextMatrixAndDetectChange = function(orderedMatrixOperations) { 482 | var fn = [ 483 | ' var transform = result.transform !== undefined ? ' + 484 | 'result.transform : (result.transform = [{ matrix: [] }]);' + 485 | ' var transformMatrix = transform[0].matrix;' 486 | ]; 487 | fn.push.apply( 488 | fn, 489 | inline(MatrixOps.unroll, ['transformMatrix'].concat(unrolledVars)) 490 | ); 491 | for (var i = 0; i < orderedMatrixOperations.length; i++) { 492 | var opName = orderedMatrixOperations[i]; 493 | if (i === 0) { 494 | fn.push.apply( 495 | fn, 496 | inline(MatrixOpsInitial[opName], ['transformMatrix', operationVar(opName)]) 497 | ); 498 | } else { 499 | fn.push.apply( 500 | fn, 501 | inline(MatrixOps[opName], ['transformMatrix', operationVar(opName)]) 502 | ); 503 | } 504 | } 505 | fn.push.apply( 506 | fn, 507 | inline(MatrixOps.matrixDiffers, ['didChange', 'transformMatrix'].concat(unrolledVars)) 508 | ); 509 | return fn; 510 | }; 511 | 512 | var InterpolateMatrix = { 513 | transformTranslate: true, 514 | transformRotateRadians: true, 515 | transformScale: true, 516 | }; 517 | 518 | var createFunctionString = function(anims) { 519 | // We must track the order they appear in so transforms are applied in the 520 | // correct order. 521 | var orderedMatrixOperations = []; 522 | 523 | // Wrapping function allows the final function to contain state (for 524 | // caching). 525 | var fn = 'return (function() {\n'; 526 | fn += createReusableOperationVars(anims); 527 | fn += 'return function(result, value) {\n'; 528 | fn += ' var didChange = false;\n'; 529 | fn += ' var nextScalarVal;\n'; 530 | fn += ' var ratio;\n'; 531 | 532 | for (var name in anims) { 533 | var anim = anims[name]; 534 | if (anim.type === 'linear') { 535 | if (InterpolateMatrix[name]) { 536 | orderedMatrixOperations.push(name); 537 | var setOperations = [ 538 | computeNextMatrixOperationField(anim, name, X_DIM, 0), 539 | computeNextMatrixOperationField(anim, name, Y_DIM, 1), 540 | computeNextMatrixOperationField(anim, name, Z_DIM, 2) 541 | ]; 542 | if (name === TRANSFORM_ROTATE_NAME) { 543 | setOperations.push(computeNextMatrixOperationField(anim, name, W_DIM, 3)); 544 | } 545 | fn += newlines(setOperations); 546 | } else { 547 | fn += computeNextValLinearScalar(anim, 'nextScalarVal'); 548 | fn += setNextValAndDetectChange(name, 'nextScalarVal'); 549 | } 550 | } else if (anim.type === 'constant') { 551 | fn += computeNextValConstant(anim); 552 | fn += setNextValAndDetectChange(name, 'nextScalarVal'); 553 | } else if (anim.type === 'step') { 554 | fn += computeNextValStep(anim); 555 | fn += setNextValAndDetectChange(name, 'nextScalarVal'); 556 | } else if (anim.type === 'identity') { 557 | fn += computeNextValIdentity(anim); 558 | fn += setNextValAndDetectChange(name, 'nextScalarVal'); 559 | } 560 | } 561 | if (orderedMatrixOperations.length) { 562 | fn += newlines(setNextMatrixAndDetectChange(orderedMatrixOperations)); 563 | } 564 | fn += ' return didChange;\n'; 565 | fn += '};\n'; 566 | fn += '})()'; 567 | return fn; 568 | }; 569 | 570 | /** 571 | * @param {object} anims Animation configuration by style property name. 572 | * @return {function} Function accepting style object, that mutates that style 573 | * object and returns a boolean describing if any update was actually applied. 574 | */ 575 | var buildStyleInterpolator = function(anims) { 576 | // Defer compiling this method until we really need it. 577 | var interpolator = null; 578 | function lazyStyleInterpolator(result, value) { 579 | if (interpolator === null) { 580 | interpolator = Function(createFunctionString(anims))(); 581 | } 582 | return interpolator(result, value); 583 | } 584 | return lazyStyleInterpolator; 585 | }; 586 | 587 | module.exports = buildStyleInterpolator; 588 | -------------------------------------------------------------------------------- /src/clamp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @typechecks 26 | */ 27 | 'use strict'; 28 | 29 | /** 30 | * @param {number} value 31 | * @param {number} min 32 | * @param {number} max 33 | * @return {number} 34 | */ 35 | function clamp(min, value, max) { 36 | if (value < min) { 37 | return min; 38 | } 39 | if (value > max) { 40 | return max; 41 | } 42 | return value; 43 | } 44 | 45 | module.exports = clamp; 46 | -------------------------------------------------------------------------------- /src/flattenStyle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @flow 26 | */ 27 | 'use strict'; 28 | 29 | var invariant = require('fbjs/lib/invariant'); 30 | 31 | import type { StyleObj } from 'StyleSheetTypes'; 32 | 33 | function getStyle(style) { 34 | if (style && typeof style === 'number') { 35 | debugger; 36 | invariant(false, "Error when using Navigator from react-native-custom-components. Please provide a raw object to `props.sceneStyle` instead of a StyleSheet reference."); 37 | } 38 | return style; 39 | } 40 | 41 | function flattenStyle(style: ?StyleObj): ?Object { 42 | if (!style) { 43 | return undefined; 44 | } 45 | invariant(style !== true, 'style may be false but not true'); 46 | 47 | if (!Array.isArray(style)) { 48 | return getStyle(style); 49 | } 50 | 51 | var result = {}; 52 | for (var i = 0, styleLength = style.length; i < styleLength; ++i) { 53 | var computedStyle = flattenStyle(style[i]); 54 | if (computedStyle) { 55 | for (var key in computedStyle) { 56 | result[key] = computedStyle[key]; 57 | } 58 | } 59 | } 60 | return result; 61 | } 62 | 63 | module.exports = flattenStyle; -------------------------------------------------------------------------------- /src/guid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 27 | /* eslint-disable no-bitwise */ 28 | 29 | 'use strict'; 30 | 31 | /** 32 | * Module that provides a function for creating a unique identifier. 33 | * The returned value does not conform to the GUID standard, but should 34 | * be globally unique in the context of the browser. 35 | */ 36 | function guid() { 37 | return 'f' + (Math.random() * (1 << 30)).toString(16).replace('.', ''); 38 | } 39 | 40 | module.exports = guid; 41 | -------------------------------------------------------------------------------- /src/merge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | */ 26 | 27 | "use strict"; 28 | 29 | var mergeInto = require('./mergeInto'); 30 | 31 | /** 32 | * Shallow merges two structures into a return value, without mutating either. 33 | * 34 | * @param {?object} one Optional object with properties to merge from. 35 | * @param {?object} two Optional object with properties to merge from. 36 | * @return {object} The shallow extension of one by two. 37 | */ 38 | var merge = function(one, two) { 39 | var result = {}; 40 | mergeInto(result, one); 41 | mergeInto(result, two); 42 | return result; 43 | }; 44 | 45 | module.exports = merge; 46 | -------------------------------------------------------------------------------- /src/mergeHelpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * requiresPolyfills: Array.isArray 26 | */ 27 | 28 | "use strict"; 29 | 30 | var invariant = require('fbjs/lib/invariant'); 31 | var keyMirror = require('fbjs/lib/keyMirror'); 32 | 33 | /** 34 | * Maximum number of levels to traverse. Will catch circular structures. 35 | * @const 36 | */ 37 | var MAX_MERGE_DEPTH = 36; 38 | 39 | /** 40 | * We won't worry about edge cases like new String('x') or new Boolean(true). 41 | * Functions are considered terminals, and arrays are not. 42 | * @param {*} o The item/object/value to test. 43 | * @return {boolean} true iff the argument is a terminal. 44 | */ 45 | var isTerminal = function(o) { 46 | return typeof o !== 'object' || o === null; 47 | }; 48 | 49 | var mergeHelpers = { 50 | 51 | MAX_MERGE_DEPTH: MAX_MERGE_DEPTH, 52 | 53 | isTerminal: isTerminal, 54 | 55 | /** 56 | * Converts null/undefined values into empty object. 57 | * 58 | * @param {?Object=} arg Argument to be normalized (nullable optional) 59 | * @return {!Object} 60 | */ 61 | normalizeMergeArg: function(arg) { 62 | return arg === undefined || arg === null ? {} : arg; 63 | }, 64 | 65 | /** 66 | * If merging Arrays, a merge strategy *must* be supplied. If not, it is 67 | * likely the caller's fault. If this function is ever called with anything 68 | * but `one` and `two` being `Array`s, it is the fault of the merge utilities. 69 | * 70 | * @param {*} one Array to merge into. 71 | * @param {*} two Array to merge from. 72 | */ 73 | checkMergeArrayArgs: function(one, two) { 74 | invariant( 75 | Array.isArray(one) && Array.isArray(two), 76 | 'Tried to merge arrays, instead got %s and %s.', 77 | one, 78 | two 79 | ); 80 | }, 81 | 82 | /** 83 | * @param {*} one Object to merge into. 84 | * @param {*} two Object to merge from. 85 | */ 86 | checkMergeObjectArgs: function(one, two) { 87 | mergeHelpers.checkMergeObjectArg(one); 88 | mergeHelpers.checkMergeObjectArg(two); 89 | }, 90 | 91 | /** 92 | * @param {*} arg 93 | */ 94 | checkMergeObjectArg: function(arg) { 95 | invariant( 96 | !isTerminal(arg) && !Array.isArray(arg), 97 | 'Tried to merge an object, instead got %s.', 98 | arg 99 | ); 100 | }, 101 | 102 | /** 103 | * @param {*} arg 104 | */ 105 | checkMergeIntoObjectArg: function(arg) { 106 | invariant( 107 | (!isTerminal(arg) || typeof arg === 'function') && !Array.isArray(arg), 108 | 'Tried to merge into an object, instead got %s.', 109 | arg 110 | ); 111 | }, 112 | 113 | /** 114 | * Checks that a merge was not given a circular object or an object that had 115 | * too great of depth. 116 | * 117 | * @param {number} Level of recursion to validate against maximum. 118 | */ 119 | checkMergeLevel: function(level) { 120 | invariant( 121 | level < MAX_MERGE_DEPTH, 122 | 'Maximum deep merge depth exceeded. You may be attempting to merge ' + 123 | 'circular structures in an unsupported way.' 124 | ); 125 | }, 126 | 127 | /** 128 | * Checks that the supplied merge strategy is valid. 129 | * 130 | * @param {string} Array merge strategy. 131 | */ 132 | checkArrayStrategy: function(strategy) { 133 | invariant( 134 | strategy === undefined || strategy in mergeHelpers.ArrayStrategies, 135 | 'You must provide an array strategy to deep merge functions to ' + 136 | 'instruct the deep merge how to resolve merging two arrays.' 137 | ); 138 | }, 139 | 140 | /** 141 | * Set of possible behaviors of merge algorithms when encountering two Arrays 142 | * that must be merged together. 143 | * - `clobber`: The left `Array` is ignored. 144 | * - `indexByIndex`: The result is achieved by recursively deep merging at 145 | * each index. (not yet supported.) 146 | */ 147 | ArrayStrategies: keyMirror({ 148 | Clobber: true, 149 | IndexByIndex: true 150 | }) 151 | 152 | }; 153 | 154 | module.exports = mergeHelpers; 155 | -------------------------------------------------------------------------------- /src/mergeInto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. All rights reserved. 3 | * 4 | * Facebook, Inc. ("Facebook") owns all right, title and interest, including 5 | * all intellectual property and other proprietary rights, in and to the React 6 | * Native CustomComponents software (the "Software"). Subject to your 7 | * compliance with these terms, you are hereby granted a non-exclusive, 8 | * worldwide, royalty-free copyright license to (1) use and copy the Software; 9 | * and (2) reproduce and distribute the Software as part of your own software 10 | * ("Your Software"). Facebook reserves all rights not expressly granted to 11 | * you in this license agreement. 12 | * 13 | * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS 14 | * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. 16 | * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR 17 | * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF 23 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | * @typechecks static-only 26 | */ 27 | 28 | "use strict"; 29 | 30 | var mergeHelpers = require('./mergeHelpers'); 31 | 32 | var checkMergeObjectArg = mergeHelpers.checkMergeObjectArg; 33 | var checkMergeIntoObjectArg = mergeHelpers.checkMergeIntoObjectArg; 34 | 35 | /** 36 | * Shallow merges two structures by mutating the first parameter. 37 | * 38 | * @param {object|function} one Object to be merged into. 39 | * @param {?object} two Optional object with properties to merge from. 40 | */ 41 | function mergeInto(one, two) { 42 | checkMergeIntoObjectArg(one); 43 | if (two != null) { 44 | checkMergeObjectArg(two); 45 | for (var key in two) { 46 | if (!two.hasOwnProperty(key)) { 47 | continue; 48 | } 49 | one[key] = two[key]; 50 | } 51 | } 52 | } 53 | 54 | module.exports = mergeInto; 55 | --------------------------------------------------------------------------------