diff --git a/.eslintignore b/.eslintignore index 15261d31c..d0528c4a5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ -**/node_modules/**/.*js +# node_modules ignored by default + **/staticBundle.js **/main.js +Libraries/vendor/**/* diff --git a/.eslintrc b/.eslintrc index da893862d..84045365c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -168,7 +168,7 @@ "no-underscore-dangle": 0, // disallow dangling underscores in identifiers "no-wrap-func": 1, // disallow wrapping of non-IIFE statements in parens "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation - "quotes": [1, "single"], // specify whether double or single quotes should be used + "quotes": [1, "single", "avoid-escape"], // specify whether double or single quotes should be used "quote-props": 0, // require quotes around object literal property names (off by default) "semi": 1, // require or disallow use of semicolons instead of ASI "sort-vars": 0, // sort variables within the same declaration block (off by default) diff --git a/IntegrationTests/AppEventsTest.js b/IntegrationTests/AppEventsTest.js new file mode 100644 index 000000000..e46b956b4 --- /dev/null +++ b/IntegrationTests/AppEventsTest.js @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AppEventsTest + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + NativeAppEventEmitter, + NativeModules, + StyleSheet, + Text, + View, +} = React; +var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager; + +var deepDiffer = require('deepDiffer'); + +var TEST_PAYLOAD = {foo: 'bar'}; + +var AppEventsTest = React.createClass({ + getInitialState: function() { + return {sent: 'none', received: 'none'}; + }, + componentDidMount: function() { + NativeAppEventEmitter.addListener('testEvent', this.receiveEvent); + var event = {data: TEST_PAYLOAD, ts: Date.now()}; + TestModule.sendAppEvent('testEvent', event); + this.setState({sent: event}); + }, + receiveEvent: function(event: any) { + if (deepDiffer(event.data, TEST_PAYLOAD)) { + throw new Error('Received wrong event: ' + JSON.stringify(event)); + } + var elapsed = (Date.now() - event.ts) + 'ms'; + this.setState({received: event, elapsed}, TestModule.markTestCompleted); + }, + render: function() { + return ( + + + {JSON.stringify(this.state, null, ' ')} + + + ); + } +}); + +var styles = StyleSheet.create({ + container: { + margin: 40, + }, +}); + +module.exports = AppEventsTest; diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index 1e61a0dbc..d769c0831 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -26,6 +26,7 @@ var TESTS = [ require('./TimersTest'), require('./AsyncStorageTest'), require('./LayoutEventsTest'), + require('./AppEventsTest'), require('./SimpleSnapshotTest'), ]; diff --git a/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m b/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m index 9bf1a4fc1..0aca49d3c 100644 --- a/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m +++ b/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m @@ -76,6 +76,11 @@ [_runner runTest:_cmd module:@"LayoutEventsTest"]; } +- (void)testAppEvents +{ + [_runner runTest:_cmd module:@"AppEventsTest"]; +} + #pragma mark Snapshot Tests - (void)testSimpleSnapshot diff --git a/Libraries/ART/ARTSerializablePath.js b/Libraries/ART/ARTSerializablePath.js index 2df8ff6bb..4e8b3c227 100644 --- a/Libraries/ART/ARTSerializablePath.js +++ b/Libraries/ART/ARTSerializablePath.js @@ -8,8 +8,7 @@ * * @providesModule ARTSerializablePath */ - -"use strict"; +'use strict'; // TODO: Move this into an ART mode called "serialized" or something diff --git a/Libraries/ART/ReactNativeART.js b/Libraries/ART/ReactNativeART.js index b2533f2a8..3b5801d00 100644 --- a/Libraries/ART/ReactNativeART.js +++ b/Libraries/ART/ReactNativeART.js @@ -8,8 +8,7 @@ * * @providesModule ReactNativeART */ - -"use strict"; +'use strict'; var Color = require('art/core/color'); var Path = require('ARTSerializablePath'); diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js index d6afa3e06..c229b60c3 100644 --- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js +++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js @@ -12,7 +12,6 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var StyleSheet = require('StyleSheet'); diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 0818a0bf9..94e0850f2 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -183,7 +183,7 @@ var ScrollResponderMixin = { var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); if (!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && - e.target != currentlyFocusedTextInput) { + e.target !== currentlyFocusedTextInput) { return true; } return this.scrollResponderIsAnimating(); @@ -244,7 +244,7 @@ var ScrollResponderMixin = { var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); if (!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && - e.target != currentlyFocusedTextInput && + e.target !== currentlyFocusedTextInput && !this.state.observedScrollSinceBecomingResponder && !this.state.becameResponderWhileAnimating) { this.props.onScrollResponderKeyboardDismissed && diff --git a/Libraries/Components/Subscribable.js b/Libraries/Components/Subscribable.js index cf9bc773a..8474b2149 100644 --- a/Libraries/Components/Subscribable.js +++ b/Libraries/Components/Subscribable.js @@ -11,7 +11,7 @@ */ 'use strict'; -var EventEmitter = require('EventEmitter'); +import type EventEmitter from 'EventEmitter'; /** * Subscribable provides a mixin for safely subscribing a component to an diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 5b3ffe1c1..dc6d05eb9 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -179,12 +179,12 @@ var TextInput = React.createClass({ 'number-pad', 'phone-pad', 'name-phone-pad', - 'email-address', 'decimal-pad', 'twitter', 'web-search', // Cross-platform 'numeric', + 'email-address', ]), /** * Determines how the return key should look. diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index c3659e961..c6a279a22 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -24,6 +24,26 @@ var createReactNativeComponentClass = require('createReactNativeComponentClass') var stylePropType = StyleSheetPropType(ViewStylePropTypes); +var AccessibilityTraits = [ + 'none', + 'button', + 'link', + 'header', + 'search', + 'image', + 'selected', + 'plays', + 'key', + 'text', + 'summary', + 'disabled', + 'frequentUpdates', + 'startsMedia', + 'adjustable', + 'allowsDirectInteraction', + 'pageTurn', +]; + /** * The most fundamental component for building UI, `View` is a * container that supports layout with flexbox, style, some touch handling, and @@ -70,6 +90,27 @@ var View = React.createClass({ */ accessibilityLabel: PropTypes.string, + /** + * Provides additional traits to screen reader. By default no traits are + * provided unless specified otherwise in element + */ + accessibilityTraits: PropTypes.oneOfType([ + PropTypes.oneOf(AccessibilityTraits), + PropTypes.arrayOf(PropTypes.oneOf(AccessibilityTraits)), + ]), + + /** + * When `accessible` is true, the system will try to invoke this function + * when the user performs accessibility tap gesture. + */ + onAcccessibilityTap: PropTypes.func, + + /** + * When `accessible` is true, the system will invoke this function when the + * user performs the magic tap gesture. + */ + onMagicTap: PropTypes.func, + /** * Used to locate this view in end-to-end tests. */ diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index a9ee724fa..18491d52c 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -75,7 +75,7 @@ var WebView = React.createClass({ errorEvent.code, errorEvent.description); } else if (this.state.viewState !== WebViewState.IDLE) { - console.error("RCTWebView invalid state encountered: " + this.state.loading); + console.error('RCTWebView invalid state encountered: ' + this.state.loading); } var webViewStyles = [styles.container, this.props.style]; @@ -152,7 +152,7 @@ var WebView = React.createClass({ onLoadingError: function(event) { event.persist(); // persist this event because we need to store it - console.error("encountered an error loading page", event.nativeEvent); + console.error('Encountered an error loading page', event.nativeEvent); this.setState({ lastErrorEvent: event.nativeEvent, diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index de9ff2ef1..83af4a8ad 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -14,7 +14,6 @@ var ActivityIndicatorIOS = require('ActivityIndicatorIOS'); var EdgeInsetsPropType = require('EdgeInsetsPropType'); var React = require('React'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); @@ -198,7 +197,7 @@ var WebView = React.createClass({ onLoadingError: function(event: Event) { event.persist(); // persist this event because we need to store it - console.error("encountered an error loading page", event.nativeEvent); + console.error('Encountered an error loading page', event.nativeEvent); this.setState({ lastErrorEvent: event.nativeEvent, diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index b8c019e32..569351745 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -24,6 +24,7 @@ * * @providesModule Navigator */ + /* eslint-disable no-extra-boolean-cast*/ 'use strict'; var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule; @@ -48,8 +49,6 @@ var clamp = require('clamp'); var flattenStyle = require('flattenStyle'); var getNavigatorContext = require('getNavigatorContext'); var invariant = require('invariant'); -var keyMirror = require('keyMirror'); -var merge = require('merge'); var rebound = require('rebound'); var PropTypes = React.PropTypes; @@ -689,7 +688,7 @@ var Navigator = React.createClass({ */ _enableScene: function(sceneIndex) { // First, determine what the defined styles are for scenes in this navigator - var sceneStyle = flattenStyle(this.props.sceneStyle); + var sceneStyle = flattenStyle([styles.baseScene, this.props.sceneStyle]); // Then restore the left value for this scene var enabledSceneNativeProps = { left: sceneStyle.left, @@ -745,7 +744,6 @@ var Navigator = React.createClass({ }, _handleMoveShouldSetPanResponder: function(e, gestureState) { - var currentRoute = this.state.routeStack[this.state.presentedIndex]; var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex]; this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState); return !! this._expectingGestureGrant; @@ -829,7 +827,16 @@ var Navigator = React.createClass({ } } else { // The gesture has enough velocity to complete, so we transition to the gesture's destination - this._transitionTo(destIndex, transitionVelocity); + this._transitionTo( + destIndex, + transitionVelocity, + null, + () => { + if (releaseGestureAction === 'pop') { + this._cleanScenesPastIndex(destIndex); + } + } + ); } this._detachGesture(); }, diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js index ed295346d..48be13ea1 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js +++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js @@ -213,7 +213,7 @@ var FromTheLeft = { translateX: { from: -SCREEN_WIDTH, to: 0, - min: 0, + min: 0, max: 1, type: 'linear', extrapolate: true, diff --git a/Libraries/Fetch/fetch.js b/Libraries/Fetch/fetch.js index 829f7c425..b5e8b151c 100644 --- a/Libraries/Fetch/fetch.js +++ b/Libraries/Fetch/fetch.js @@ -12,6 +12,7 @@ * @providesModule fetch * @nolint */ +/* eslint-disable */ 'use strict'; var self = {}; diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js index fae309aef..80dbfa19b 100644 --- a/Libraries/Geolocation/Geolocation.js +++ b/Libraries/Geolocation/Geolocation.js @@ -120,6 +120,6 @@ var Geolocation = { subscriptions = []; } } -} +}; module.exports = Geolocation; diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 45c1ad828..9b84937af 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -60,7 +60,7 @@ var Image = React.createClass({ /** * `uri` is a string representing the resource identifier for the image, which * could be an http address, a local file path, or the name of a static image - * resource (which should be wrapped in the `required('image!name')` function). + * resource (which should be wrapped in the `require('image!name')` function). */ source: PropTypes.shape({ uri: PropTypes.string, diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js index 79a5e818c..311e573fc 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js @@ -18,6 +18,7 @@ * and wrapping resulting file into `wrapper` function. * */ +/*eslint-disable */ var scope = {}; wrapper.call(scope); diff --git a/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js b/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js index 8602288ee..98610724a 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js +++ b/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js @@ -11,6 +11,7 @@ * * @nolint */ +/* eslint-disable */ (function() { var define = null; // Hack to make it work with our packager diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 9432fbef3..4d03c6641 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -11,7 +11,6 @@ */ 'use strict'; -var NativeModules = require('NativeModules'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var RCTPushNotificationManager = require('NativeModules').PushNotificationManager; var invariant = require('invariant'); diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 33f562515..2508b88fe 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -11,6 +11,7 @@ #import "FBSnapshotTestController.h" #import "RCTAssert.h" +#import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTUIManager.h" @@ -63,4 +64,9 @@ RCT_EXPORT_METHOD(markTestCompleted) }]; } +RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(id)body) +{ + [_bridge.eventDispatcher sendAppEventWithName:name body:body]; +} + @end diff --git a/Libraries/RKBackendNode/queryLayoutByID.js b/Libraries/RKBackendNode/queryLayoutByID.js index d9b1ed8cc..d6492e6da 100644 --- a/Libraries/RKBackendNode/queryLayoutByID.js +++ b/Libraries/RKBackendNode/queryLayoutByID.js @@ -58,4 +58,3 @@ var queryLayoutByID = function( }; module.exports = queryLayoutByID; - diff --git a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js index b585db5a7..55b22a959 100644 --- a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -9,8 +9,7 @@ * @providesModule IOSNativeBridgeEventPlugin * @flow */ - -"use strict"; +'use strict'; var EventPropagators = require('EventPropagators'); var NativeModules = require('NativeModules'); @@ -33,7 +32,7 @@ for (var bubblingTypeName in customBubblingEventTypes) { for (var directTypeName in customDirectEventTypes) { warning( !customBubblingEventTypes[directTypeName], - "Event cannot be both direct and bubbling: %s", + 'Event cannot be both direct and bubbling: %s', directTypeName ); allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName]; @@ -76,4 +75,3 @@ var IOSNativeBridgeEventPlugin = { }; module.exports = IOSNativeBridgeEventPlugin; - diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js index 6ee23cda8..5fd3ea1b8 100644 --- a/Libraries/ReactIOS/verifyPropTypes.js +++ b/Libraries/ReactIOS/verifyPropTypes.js @@ -22,6 +22,13 @@ function verifyPropTypes( if (!viewConfig) { return; // This happens for UnimplementedView. } + var componentName = component.name || component.displayName; + if (!component.propTypes) { + throw new Error( + '`' + componentName + '` has no propTypes defined`' + ); + } + var nativeProps = viewConfig.nativeProps; for (var prop in nativeProps) { if (!component.propTypes[prop] && @@ -29,9 +36,9 @@ function verifyPropTypes( !ReactNativeStyleAttributes[prop] && (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { throw new Error( - '`' + component.displayName + '` has no propType for native prop `' + + '`' + componentName + '` has no propType for native prop `' + viewConfig.uiViewClassName + '.' + prop + '` of native type `' + - nativeProps[prop].type + '`' + nativeProps[prop] + '`' ); } } diff --git a/Libraries/ReactNative/React.js b/Libraries/ReactNative/React.js index 1eeeb32c3..9cd4464f2 100644 --- a/Libraries/ReactNative/React.js +++ b/Libraries/ReactNative/React.js @@ -9,7 +9,6 @@ * @providesModule React * @flow */ - -"use strict"; +'use strict'; module.exports = require('ReactNative'); diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js index abe31a361..970944baa 100644 --- a/Libraries/ReactNative/ReactNative.js +++ b/Libraries/ReactNative/ReactNative.js @@ -9,7 +9,7 @@ * @providesModule ReactNative * @flow */ -"use strict"; +'use strict'; var ReactChildren = require('ReactChildren'); var ReactClass = require('ReactClass'); diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index dcc31a2b3..95af29023 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -63,7 +63,8 @@ var cachedIndexArray = function(size) { for (var i = 0; i < size; i++) { arr[i] = i; } - return cachedIndexArray._cache[size] = arr; + cachedIndexArray._cache[size] = arr; + return arr; } else { return cachedResult; } @@ -228,7 +229,7 @@ ReactNativeBaseComponent.Mixin = { */ _reconcileListenersUponUpdate: function(prevProps, nextProps) { for (var key in nextProps) { - if (registrationNames[key] && (nextProps[key] != prevProps[key])) { + if (registrationNames[key] && (nextProps[key] !== prevProps[key])) { putListener(this._rootNodeID, key, nextProps[key]); } } diff --git a/Libraries/ReactNative/ReactNativeDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js index 3b47d4d8f..af17f9e07 100644 --- a/Libraries/ReactNative/ReactNativeDOMIDOperations.js +++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js @@ -9,8 +9,7 @@ * @providesModule ReactNativeDOMIDOperations * @flow */ - -"use strict"; +'use strict'; var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js index 3b15a8cc9..ce040c779 100644 --- a/Libraries/ReactNative/ReactNativeDefaultInjection.js +++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js @@ -9,8 +9,7 @@ * @providesModule ReactNativeDefaultInjection * @flow */ - -"use strict"; +'use strict'; /** * Make sure `setTimeout`/`setInterval` are patched correctly. @@ -21,7 +20,6 @@ var EventPluginUtils = require('EventPluginUtils'); var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder'); var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin'); var NodeHandle = require('NodeHandle'); -var ReactClass = require('ReactClass'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactEmptyComponent = require('ReactEmptyComponent'); diff --git a/Libraries/ReactNative/ReactNativeEventEmitter.js b/Libraries/ReactNative/ReactNativeEventEmitter.js index 9bd344e79..b8f773100 100644 --- a/Libraries/ReactNative/ReactNativeEventEmitter.js +++ b/Libraries/ReactNative/ReactNativeEventEmitter.js @@ -9,8 +9,7 @@ * @providesModule ReactNativeEventEmitter * @flow */ - -"use strict"; +'use strict'; var EventPluginHub = require('EventPluginHub'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 004a3fbda..82efa57a6 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -21,7 +21,6 @@ var ReactUpdates = require('ReactUpdates'); var emptyObject = require('emptyObject'); var instantiateReactComponent = require('instantiateReactComponent'); -var invariant = require('invariant'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); function instanceNumberToChildRootID(rootNodeID, instanceNumber) { diff --git a/Libraries/ReactNative/ReactNativeReconcileTransaction.js b/Libraries/ReactNative/ReactNativeReconcileTransaction.js index ac9ed657b..309630e3c 100644 --- a/Libraries/ReactNative/ReactNativeReconcileTransaction.js +++ b/Libraries/ReactNative/ReactNativeReconcileTransaction.js @@ -9,8 +9,7 @@ * @providesModule ReactNativeReconcileTransaction * @flow */ - -"use strict"; +'use strict'; var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js index d154d045f..50b839e1d 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -19,8 +19,11 @@ ReactNativeViewAttributes.UIView = { pointerEvents: true, accessible: true, accessibilityLabel: true, + accessibilityTraits: true, testID: true, onLayout: true, + onAccessibilityTap: true, + onMagicTap: true, }; ReactNativeViewAttributes.RCTView = merge( diff --git a/Libraries/ReactNative/createReactNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js index 3a63089b6..c821cfa75 100644 --- a/Libraries/ReactNative/createReactNativeComponentClass.js +++ b/Libraries/ReactNative/createReactNativeComponentClass.js @@ -10,9 +10,8 @@ * @flow */ -"use strict"; +'use strict'; -var ReactElement = require('ReactElement'); var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); // See also ReactNativeBaseComponent diff --git a/Libraries/Settings/Settings.ios.js b/Libraries/Settings/Settings.ios.js index c1099df93..89388be57 100644 --- a/Libraries/Settings/Settings.ios.js +++ b/Libraries/Settings/Settings.ios.js @@ -31,7 +31,7 @@ var Settings = { }, watchKeys(keys: string | Array, callback: Function): number { - if (typeof keys == 'string') { + if (typeof keys === 'string') { keys = [keys]; } @@ -41,7 +41,7 @@ var Settings = { ); var sid = subscriptions.length; - subscriptions.push({keys: keys, callback: callback}) + subscriptions.push({keys: keys, callback: callback}); return sid; }, @@ -52,15 +52,14 @@ var Settings = { }, _sendObservations(body: Object) { - var _this = this; Object.keys(body).forEach((key) => { var newValue = body[key]; - var didChange = _this._settings[key] !== newValue; - _this._settings[key] = newValue; + var didChange = this._settings[key] !== newValue; + this._settings[key] = newValue; if (didChange) { subscriptions.forEach((sub) => { - if (~sub.keys.indexOf(key) && sub.callback) { + if (sub.keys.indexOf(key) !== -1 && sub.callback) { sub.callback(); } }); diff --git a/Libraries/StyleSheet/EdgeInsetsPropType.js b/Libraries/StyleSheet/EdgeInsetsPropType.js index 3089fe7bd..1850ae611 100644 --- a/Libraries/StyleSheet/EdgeInsetsPropType.js +++ b/Libraries/StyleSheet/EdgeInsetsPropType.js @@ -9,12 +9,11 @@ * @providesModule EdgeInsetsPropType * @flow */ -'use strict' +'use strict'; var PropTypes = require('ReactPropTypes'); var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); -var insetsDiffer = require('insetsDiffer'); var EdgeInsetsPropType = createStrictShapeTypeChecker({ top: PropTypes.number, diff --git a/Libraries/StyleSheet/PointPropType.js b/Libraries/StyleSheet/PointPropType.js index 1e8fe95e9..606482dd5 100644 --- a/Libraries/StyleSheet/PointPropType.js +++ b/Libraries/StyleSheet/PointPropType.js @@ -9,12 +9,11 @@ * @providesModule PointPropType * @flow */ -'use strict' +'use strict'; var PropTypes = require('ReactPropTypes'); var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); -var pointsDiffer = require('pointsDiffer'); var PointPropType = createStrictShapeTypeChecker({ x: PropTypes.number, diff --git a/Libraries/Utilities/ErrorUtils.js b/Libraries/Utilities/ErrorUtils.js index 666d536be..b66b08546 100644 --- a/Libraries/Utilities/ErrorUtils.js +++ b/Libraries/Utilities/ErrorUtils.js @@ -8,6 +8,7 @@ * * @providesModule ErrorUtils */ +/* eslint-disable consistent-this, global-strict */ var GLOBAL = this; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index a7f082604..0550eb187 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -3,6 +3,7 @@ * * @providesModule MatrixMath */ +/* eslint-disable space-infix-ops */ 'use strict'; var invariant = require('invariant'); diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js index e5a8db49c..b76a91a58 100644 --- a/Libraries/Utilities/RCTLog.js +++ b/Libraries/Utilities/RCTLog.js @@ -9,7 +9,6 @@ * @providesModule RCTLog * @flow */ - /* globals nativeLoggingHook */ 'use strict'; var invariant = require('invariant'); diff --git a/Libraries/Utilities/RCTRenderingPerf.js b/Libraries/Utilities/RCTRenderingPerf.js index c6466aa64..126c8a032 100644 --- a/Libraries/Utilities/RCTRenderingPerf.js +++ b/Libraries/Utilities/RCTRenderingPerf.js @@ -12,7 +12,6 @@ 'use strict'; var ReactDefaultPerf = require('ReactDefaultPerf'); -var ReactPerf = require('ReactPerf'); var invariant = require('invariant'); diff --git a/Libraries/Utilities/__mocks__/ErrorUtils.js b/Libraries/Utilities/__mocks__/ErrorUtils.js index 99db79177..59fbbafbc 100644 --- a/Libraries/Utilities/__mocks__/ErrorUtils.js +++ b/Libraries/Utilities/__mocks__/ErrorUtils.js @@ -6,7 +6,7 @@ function execute(fun, context, args) { return fun.apply(context, args); -}; +} function reportError(error) { throw error; diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js index 67f07cb41..5e6515cc0 100644 --- a/Libraries/Utilities/buildStyleInterpolator.js +++ b/Libraries/Utilities/buildStyleInterpolator.js @@ -7,6 +7,7 @@ /** * Cannot "use strict" because we must use eval in this file. */ +/* eslint-disable global-strict */ var keyOf = require('keyOf'); @@ -372,7 +373,7 @@ var MatrixOpsInitial = { var setNextValAndDetectChange = function(name, tmpVarName) { return ( ' if (!didChange) {\n' + - ' var prevVal = result.' + name +';\n' + + ' var prevVal = result.' + name + ';\n' + ' result.' + name + ' = ' + tmpVarName + ';\n' + ' didChange = didChange || (' + tmpVarName + ' !== prevVal);\n' + ' } else {\n' + diff --git a/Libraries/Utilities/truncate.js b/Libraries/Utilities/truncate.js index 1d318e835..a15da571b 100644 --- a/Libraries/Utilities/truncate.js +++ b/Libraries/Utilities/truncate.js @@ -45,4 +45,3 @@ var truncate = function( }; module.exports = truncate; - diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index dd99ac9fb..0f73c2829 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -141,6 +141,7 @@ RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath); * Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these. */ RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); +RCT_EXTERN NSNumber *RCTConvertMultiEnumValue(const char *, NSDictionary *, NSNumber *, id); RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id); RCT_EXTERN void RCTLogConvertError(id, const char *); @@ -194,6 +195,21 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) return [RCTConvertEnumValue(#type, mapping, @(default), json) getter]; \ } +/** + * This macro is used for creating converters for enum types for + * multiple enum values combined with | operator + */ +#define RCT_MULTI_ENUM_CONVERTER(type, values, default, getter) \ ++ (type)type:(id)json \ +{ \ + static NSDictionary *mapping; \ + static dispatch_once_t onceToken; \ + dispatch_once(&onceToken, ^{ \ + mapping = values; \ + }); \ + return [RCTConvertMultiEnumValue(#type, mapping, @(default), json) getter]; \ +} + /** * This macro is used for creating converter functions for typed arrays. */ diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 06b5aa023..0047f8461 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -175,6 +175,22 @@ NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNum return value ?: defaultValue; } +NSNumber *RCTConvertMultiEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) +{ + if ([json isKindOfClass:[NSArray class]]) { + if ([json count] == 0) { + return defaultValue; + } + long long result = 0; + for (id arrayElement in json) { + NSNumber *value = RCTConvertEnumValue(typeName, mapping, defaultValue, arrayElement); + result |= [value longLongValue]; + } + return @(result); + } + return RCTConvertEnumValue(typeName, mapping, defaultValue, json); +} + RCT_ENUM_CONVERTER(NSTextAlignment, (@{ @"auto": @(NSTextAlignmentNatural), @"left": @(NSTextAlignmentLeft), diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 7d6925b12..e9734c204 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1309,6 +1309,12 @@ RCT_EXPORT_METHOD(clearJSResponder) @"topLoadingError": @{ @"registrationName": @"onLoadingError" }, + @"topAccessibilityTap": @{ + @"registrationName": @"onAccessibilityTap" + }, + @"topMagicTap": @{ + @"registrationName": @"onMagicTap" + }, } mutableCopy]; [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) { diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index b8da37b15..51d060ca3 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -15,8 +15,14 @@ @protocol RCTAutoInsetsProtocol; +@class RCTView; +typedef void (^RCTViewEventHandler)(RCTView *view); + @interface RCTView : UIView +@property (nonatomic, copy) RCTViewEventHandler accessibilityTapHandler; +@property (nonatomic, copy) RCTViewEventHandler magicTapHandler; + /** * Used to control how touch events are processed. */ diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index c2cd04703..d6394f2c6 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -167,6 +167,26 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) } } +- (BOOL)accessibilityActivate +{ + if (self.accessibilityTapHandler) { + self.accessibilityTapHandler(self); + return YES; + } else { + return NO; + } +} + +- (BOOL)accessibilityPerformMagicTap +{ + if (self.magicTapHandler) { + self.magicTapHandler(self); + return YES; + } else { + return NO; + } +} + #pragma mark - Statics for dealing with layoutGuides + (void)autoAdjustInsetsForView:(UIView *)parentView diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index cb42c7ec5..8230e398d 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -17,6 +17,31 @@ #import "RCTUIManager.h" #import "RCTUtils.h" #import "RCTView.h" +#import "UIView+React.h" + +@implementation RCTConvert(UIAccessibilityTraits) + +RCT_MULTI_ENUM_CONVERTER(UIAccessibilityTraits, (@{ + @"none": @(UIAccessibilityTraitNone), + @"button": @(UIAccessibilityTraitButton), + @"link": @(UIAccessibilityTraitLink), + @"header": @(UIAccessibilityTraitHeader), + @"search": @(UIAccessibilityTraitSearchField), + @"image": @(UIAccessibilityTraitImage), + @"selected": @(UIAccessibilityTraitSelected), + @"plays": @(UIAccessibilityTraitPlaysSound), + @"key": @(UIAccessibilityTraitKeyboardKey), + @"text": @(UIAccessibilityTraitStaticText), + @"summary": @(UIAccessibilityTraitSummaryElement), + @"disabled": @(UIAccessibilityTraitNotEnabled), + @"frequentUpdates": @(UIAccessibilityTraitUpdatesFrequently), + @"startsMedia": @(UIAccessibilityTraitStartsMediaSession), + @"adjustable": @(UIAccessibilityTraitAdjustable), + @"allowsDirectInteraction": @(UIAccessibilityTraitAllowsDirectInteraction), + @"pageTurn": @(UIAccessibilityTraitCausesPageTurn), + }), UIAccessibilityTraitNone, unsignedLongLongValue) + +@end @implementation RCTViewManager @@ -67,6 +92,7 @@ RCT_EXPORT_MODULE() #pragma mark - View properties RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString) +RCT_EXPORT_VIEW_PROPERTY(accessibilityTraits, UIAccessibilityTraits) RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor) RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement, BOOL) RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString) @@ -146,6 +172,27 @@ RCT_CUSTOM_VIEW_PROPERTY(borderWidth, CGFloat, RCTView) view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth; } } +RCT_CUSTOM_VIEW_PROPERTY(onAccessibilityTap, BOOL, RCTView) +{ + view.accessibilityTapHandler = [self eventHandlerWithName:@"topAccessibilityTap" json:json]; +} +RCT_CUSTOM_VIEW_PROPERTY(onMagicTap, BOOL, RCTView) +{ + view.magicTapHandler = [self eventHandlerWithName:@"topMagicTap" json:json]; +} + +- (RCTViewEventHandler)eventHandlerWithName:(NSString *)eventName json:(id)json +{ + RCTViewEventHandler handler = nil; + if ([RCTConvert BOOL:json]) { + __weak RCTViewManager *weakSelf = self; + handler = ^(RCTView *tappedView) { + NSDictionary *body = @{ @"target": tappedView.reactTag }; + [weakSelf.bridge.eventDispatcher sendInputEventWithName:eventName body:body]; + }; + } + return handler; +} #define RCT_VIEW_BORDER_PROPERTY(SIDE) \ RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, CGFloat, RCTView) \ diff --git a/package.json b/package.json index 3fffc5491..6c4fa493d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ ], "scripts": { "test": "jest", - "lint": "node linter.js Examples/", + "lint": "node linter.js Examples/ Libraries/", "start": "./packager/packager.sh" }, "bin": {