diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index a52284b85ec11f..4e54c38b827a54 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -174,6 +174,89 @@ exports.examples = [ ); } }, + { + title: 'Keyboard types', + render: function() { + var keyboardTypes = [ + 'default', + 'ascii-capable', + 'numbers-and-punctuation', + 'url', + 'number-pad', + 'phone-pad', + 'name-phone-pad', + 'email-address', + 'decimal-pad', + 'twitter', + 'web-search', + 'numeric', + ]; + var examples = keyboardTypes.map((type) => { + return ( + + + + ); + }); + return {examples}; + } + }, + { + title: 'Return key types', + render: function() { + var returnKeyTypes = [ + 'default', + 'go', + 'google', + 'join', + 'next', + 'route', + 'search', + 'send', + 'yahoo', + 'done', + 'emergency-call', + ]; + var examples = returnKeyTypes.map((type) => { + return ( + + + + ); + }); + return {examples}; + } + }, + { + title: 'Enable return key automatically', + render: function() { + return ( + + + + + + ); + } + }, + { + title: 'Secure text entry', + render: function() { + return ( + + + + + + ); + } + }, { title: 'Event handling', render: function(): ReactElement { return }, diff --git a/Libraries/Components/TextInput/TextInput.ios.js b/Libraries/Components/TextInput/TextInput.ios.js index 02fec4d5a3a700..e73debbb0ee8b6 100644 --- a/Libraries/Components/TextInput/TextInput.ios.js +++ b/Libraries/Components/TextInput/TextInput.ios.js @@ -33,6 +33,8 @@ var merge = require('merge'); var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType; var clearButtonModeConsts = RCTUIManager.UITextField.clearButtonMode; +var keyboardTypeConsts = RCTUIManager.UIKeyboardType; +var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType; var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { autoCorrect: true, @@ -44,6 +46,9 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { fontStyle: true, fontWeight: true, keyboardType: true, + returnKeyType: true, + enablesReturnKeyAutomatically: true, + secureTextEntry: true, mostRecentEventCounter: true, placeholder: true, placeholderTextColor: true, @@ -66,6 +71,10 @@ var notMultiline = { onSubmitEditing: true, }; +var crossPlatformKeyboardTypeMap = { + 'numeric': 'decimal-pad', +}; + type DefaultProps = { bufferDelay: number; }; @@ -138,8 +147,47 @@ var TextInput = React.createClass({ */ keyboardType: PropTypes.oneOf([ 'default', + // iOS + 'ascii-capable', + 'numbers-and-punctuation', + 'url', + 'number-pad', + 'phone-pad', + 'name-phone-pad', + 'email-address', + 'decimal-pad', + 'twitter', + 'web-search', + // Cross-platform 'numeric', ]), + /** + * Determines how the return key should look. + */ + returnKeyType: PropTypes.oneOf([ + 'default', + 'go', + 'google', + 'join', + 'next', + 'route', + 'search', + 'send', + 'yahoo', + 'done', + 'emergency-call', + ]), + /** + * If true, the keyboard disables the return key when there is no text and + * automatically enables it when there is text. Default value is false. + */ + enablesReturnKeyAutomatically: PropTypes.bool, + + /** + * If true, the text input obscures the text entered so that sensitive text + * like passwords stay secure. Default value is false. + */ + secureTextEntry: PropTypes.bool, /** * If true, the text input can be multiple lines. Default value is false. */ @@ -317,6 +365,11 @@ var TextInput = React.createClass({ var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; var clearButtonMode = clearButtonModeConsts[this.props.clearButtonMode]; + var keyboardType = keyboardTypeConsts[ + crossPlatformKeyboardTypeMap[this.props.keyboardType] || + this.props.keyboardType + ]; + var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType]; if (!this.props.multiline) { for (var propKey in onlyMultiline) { @@ -331,7 +384,10 @@ var TextInput = React.createClass({ ref="input" style={[styles.input, this.props.style]} enabled={this.props.editable} - keyboardType={this.props.keyboardType} + keyboardType={keyboardType} + returnKeyType={returnKeyType} + enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically} + secureTextEntry={this.props.secureTextEntry} onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} @@ -373,6 +429,10 @@ var TextInput = React.createClass({ children={children} mostRecentEventCounter={this.state.mostRecentEventCounter} editable={this.props.editable} + keyboardType={keyboardType} + returnKeyType={returnKeyType} + enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically} + secureTextEntry={this.props.secureTextEntry} onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} diff --git a/README.md b/README.md index fd0ec31eee6cc9..c8dbf0814a3d90 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# React Native [![Build Status](https://magnum.travis-ci.com/facebook/react-native.svg?token=L5Egb3B4dyQzH5wDijCB&branch=master)](https://magnum.travis-ci.com/facebook/react-native) +# React Native [![Build Status](https://travis-ci.org/facebook/react-native.svg?branch=master)](https://travis-ci.org/facebook/react-native) React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and [React](http://facebook.github.io/react). The focus of React Native is on developer efficiency across all the platforms you care about - learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native. ## Native iOS Components -With React Native, you can use the standard platform components such as UITabBar and UINavigationController on iOS. This gives your app a consistent look and feel with the rest of the platform ecosystem, and keeps the quality bar high. These components are easily incorporated into your app using their React component counterparts, such as TabBarIOS and NavigatorIOS. +With React Native, you can use the standard platform components such as `UITabBar` and `UINavigationController` on iOS. This gives your app a consistent look and feel with the rest of the platform ecosystem, and keeps the quality bar high. These components are easily incorporated into your app using their React component counterparts, such as _TabBarIOS_ and _NavigatorIOS_. ```javascript var React = require('react-native'); @@ -54,7 +54,7 @@ var TouchDemo = React.createClass({ ## Flexbox and Styling -Laying out views should be easy, which is why we brought the flexbox layout model from the web to React Native. Flexbox makes it simple to build the most common UI layouts, such as stacked and nested boxes with margin and padding. React Native also supports common web syles, such as `fontWeight`, and the `StyleSheet` abstraction provides an optimized mechanism to declare all your styles and layout right along with the components that use them and apply them inline. +Laying out views should be easy, which is why we brought the flexbox layout model from the web to React Native. Flexbox makes it simple to build the most common UI layouts, such as stacked and nested boxes with margin and padding. React Native also supports common web styles, such as `fontWeight`, and the `StyleSheet` abstraction provides an optimized mechanism to declare all your styles and layout right along with the components that use them and apply them inline. ```javascript var React = require('react-native'); @@ -161,7 +161,7 @@ var Message = React.createClass({ }); ``` -Custom iOS views can be exposed by subclassing `RCTViewManager`, implementing a `- (UIView *)view` method, and exporting properties with the `RCT_EXPORT_VIEW_PROPERTY` macro. Then a simple JavaScript file connects the dots. +Custom iOS views can be exposed by subclassing `RCTViewManager`, implementing a `-view` method, and exporting properties with the `RCT_EXPORT_VIEW_PROPERTY` macro. Then a simple JavaScript file connects the dots. ```objc // Objective-C diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index c8d6ecadab5501..0f2ecf68ed0dc9 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -51,6 +51,7 @@ + (UITextFieldViewMode)UITextFieldViewMode:(id)json; + (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; + (UIKeyboardType)UIKeyboardType:(id)json; ++ (UIReturnKeyType)UIReturnKeyType:(id)json; + (UIViewContentMode)UIViewContentMode:(id)json; + (UIBarStyle)UIBarStyle:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 9bf7104c996b0d..36de37f21c50c2 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -116,10 +116,33 @@ + (NSURLRequest *)NSURLRequest:(id)json }), UIScrollViewKeyboardDismissModeNone, integerValue) RCT_ENUM_CONVERTER(UIKeyboardType, (@{ - @"numeric": @(UIKeyboardTypeDecimalPad), @"default": @(UIKeyboardTypeDefault), + @"ascii-capable": @(UIKeyboardTypeASCIICapable), + @"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation), + @"url": @(UIKeyboardTypeURL), + @"number-pad": @(UIKeyboardTypeNumberPad), + @"phone-pad": @(UIKeyboardTypePhonePad), + @"name-phone-pad": @(UIKeyboardTypeNamePhonePad), + @"email-address": @(UIKeyboardTypeEmailAddress), + @"decimal-pad": @(UIKeyboardTypeDecimalPad), + @"twitter": @(UIKeyboardTypeTwitter), + @"web-search": @(UIKeyboardTypeWebSearch), }), UIKeyboardTypeDefault, integerValue) +RCT_ENUM_CONVERTER(UIReturnKeyType, (@{ + @"default": @(UIReturnKeyDefault), + @"go": @(UIReturnKeyGo), + @"google": @(UIReturnKeyGoogle), + @"join": @(UIReturnKeyJoin), + @"next": @(UIReturnKeyNext), + @"route": @(UIReturnKeyRoute), + @"search": @(UIReturnKeySearch), + @"send": @(UIReturnKeySend), + @"yahoo": @(UIReturnKeyYahoo), + @"done": @(UIReturnKeyDone), + @"emergency-call": @(UIReturnKeyEmergencyCall), +}), UIReturnKeyDefault, integerValue) + RCT_ENUM_CONVERTER(UIViewContentMode, (@{ @"scale-to-fill": @(UIViewContentModeScaleToFill), @"scale-aspect-fit": @(UIViewContentModeScaleAspectFit), diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index a0e7285c6b0dfb..ae6e54d69404c0 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1291,6 +1291,32 @@ - (NSDictionary *)constantsToExport @"always": @(UITextFieldViewModeAlways), }, }, + @"UIKeyboardType": @{ + @"default": @(UIKeyboardTypeDefault), + @"ascii-capable": @(UIKeyboardTypeASCIICapable), + @"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation), + @"url": @(UIKeyboardTypeURL), + @"number-pad": @(UIKeyboardTypeNumberPad), + @"phone-pad": @(UIKeyboardTypePhonePad), + @"name-phone-pad": @(UIKeyboardTypeNamePhonePad), + @"decimal-pad": @(UIKeyboardTypeDecimalPad), + @"email-address": @(UIKeyboardTypeEmailAddress), + @"twitter": @(UIKeyboardTypeTwitter), + @"web-search": @(UIKeyboardTypeWebSearch), + }, + @"UIReturnKeyType": @{ + @"default": @(UIReturnKeyDefault), + @"go": @(UIReturnKeyGo), + @"google": @(UIReturnKeyGoogle), + @"join": @(UIReturnKeyJoin), + @"next": @(UIReturnKeyNext), + @"route": @(UIReturnKeyRoute), + @"search": @(UIReturnKeySearch), + @"send": @(UIReturnKeySend), + @"yahoo": @(UIReturnKeyYahoo), + @"done": @(UIReturnKeyDone), + @"emergency-call": @(UIReturnKeyEmergencyCall), + }, @"UIView": @{ @"ContentMode": @{ @"ScaleToFill": @(UIViewContentModeScaleToFill), diff --git a/React/Views/RCTTextField.m b/React/Views/RCTTextField.m index 1ccf17d7f5f658..953c3421cd6118 100644 --- a/React/Views/RCTTextField.m +++ b/React/Views/RCTTextField.m @@ -31,7 +31,6 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher [self addTarget:self action:@selector(_textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd]; [self addTarget:self action:@selector(_textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit]; _reactSubviews = [[NSMutableArray alloc] init]; - self.returnKeyType = UIReturnKeyDone; } return self; } diff --git a/React/Views/RCTTextFieldManager.m b/React/Views/RCTTextFieldManager.m index 087dc5b26421d2..ddf6d6f924f9e9 100644 --- a/React/Views/RCTTextFieldManager.m +++ b/React/Views/RCTTextFieldManager.m @@ -29,6 +29,9 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(text, NSString) RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode) RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) +RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) +RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) +RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField) diff --git a/packager/package.json b/packager/package.json index 6bfb6a2f8f4f02..8ad2e871855f54 100644 --- a/packager/package.json +++ b/packager/package.json @@ -1,6 +1,6 @@ { "name": "react-native-cli", - "version": "0.1.1", + "version": "0.1.2", "description": "Build native apps with React!", "repository": { "type": "git", diff --git a/react-native-cli/package.json b/react-native-cli/package.json index e0e19dd721667e..8644e7eae217ca 100644 --- a/react-native-cli/package.json +++ b/react-native-cli/package.json @@ -1,6 +1,6 @@ { "name": "react-native-cli", - "version": "0.0.0", + "version": "0.1.3", "description": "The ReactNative cli tools", "main": "index.js", "bin": { diff --git a/website/jsdocs/TypeExpressionParser.js b/website/jsdocs/TypeExpressionParser.js index 43b3b9f00867a9..2ded51ede70284 100644 --- a/website/jsdocs/TypeExpressionParser.js +++ b/website/jsdocs/TypeExpressionParser.js @@ -8,7 +8,7 @@ */ /*global exports:true*/ -"use strict"; +'use strict'; var Syntax = require('esprima-fb').Syntax; @@ -25,7 +25,7 @@ function reverseObject(/*object*/ object) /*object*/ { var reversed = {}; for (var key in object) { if (object.hasOwnProperty(key)) { - reversed[object[key]] = key + reversed[object[key]] = key; } } return reversed; @@ -452,16 +452,16 @@ exports.popTypeVariables = popTypeVariables; function fromFlowAnnotation(/*object*/ annotation, state) /*?object*/ { var ast; switch (annotation.type) { - case "NumberTypeAnnotation": - return createAst(SYMBOLS.SIMPLE, "number", 0); - case "StringTypeAnnotation": - return createAst(SYMBOLS.SIMPLE, "string", 0); - case "BooleanTypeAnnotation": - return createAst(SYMBOLS.SIMPLE, "boolean", 0); - case "AnyTypeAnnotation": // fallthrough - case "VoidTypeAnnotation": + case 'NumberTypeAnnotation': + return createAst(SYMBOLS.SIMPLE, 'number', 0); + case 'StringTypeAnnotation': + return createAst(SYMBOLS.SIMPLE, 'string', 0); + case 'BooleanTypeAnnotation': + return createAst(SYMBOLS.SIMPLE, 'boolean', 0); + case 'AnyTypeAnnotation': // fallthrough + case 'VoidTypeAnnotation': return null; - case "NullableTypeAnnotation": + case 'NullableTypeAnnotation': ast = fromFlowAnnotation(annotation.typeAnnotation, state); if (ast) { ast.nullable = true; @@ -486,10 +486,10 @@ function fromFlowAnnotation(/*object*/ annotation, state) /*?object*/ { // to render a simple function instead of a detailed one if ((params.length || returnType) && params.length === annotation.params.length) { - return createAst(SYMBOLS.FUNCTION, [params, returnType], 0) + return createAst(SYMBOLS.FUNCTION, [params, returnType], 0); } return createAst(SYMBOLS.SIMPLE, 'function', 0); - case "GenericTypeAnnotation": + case 'GenericTypeAnnotation': var alias = getTypeAlias(annotation.id, state); if (alias) { return fromFlowAnnotation(alias, state); @@ -497,7 +497,7 @@ function fromFlowAnnotation(/*object*/ annotation, state) /*?object*/ { // Qualified type identifiers are not handled by runtime typechecker, // so simply omit the annotation for now. - if (annotation.id.type === "QualifiedTypeIdentifier") { + if (annotation.id.type === 'QualifiedTypeIdentifier') { return null; } @@ -521,12 +521,12 @@ function fromFlowAnnotation(/*object*/ annotation, state) /*?object*/ { ); switch (name) { - case "mixed": // fallthrough - case "$Enum": + case 'mixed': // fallthrough + case '$Enum': // Not supported return null; - case "array": // fallthrough - case "promise": + case 'array': // fallthrough + case 'promise': if (annotation.typeParameters) { var parametricAst = fromFlowAnnotation( annotation.typeParameters.params[0], diff --git a/website/jsdocs/findExportDefinition.js b/website/jsdocs/findExportDefinition.js index 72a0cbe7de65c3..4a6f6a0fe00cc3 100644 --- a/website/jsdocs/findExportDefinition.js +++ b/website/jsdocs/findExportDefinition.js @@ -8,7 +8,7 @@ */ /*jslint node: true */ -"use strict"; +'use strict'; var esprima = require('esprima-fb'); var Syntax = esprima.Syntax; @@ -278,6 +278,6 @@ function findExportDefinition(ast) { } return null; -}; +} module.exports = findExportDefinition; diff --git a/website/jsdocs/generic-function-visitor.js b/website/jsdocs/generic-function-visitor.js index 0cb98ba8614ffb..e9719c3c1f68be 100644 --- a/website/jsdocs/generic-function-visitor.js +++ b/website/jsdocs/generic-function-visitor.js @@ -9,7 +9,7 @@ /*global exports:true*/ /*jslint node:true*/ -"use strict"; +'use strict'; var util = require('util'); @@ -278,7 +278,7 @@ function renderParams(/*?object*/ params) /*string*/ { var returnParam = '\"returns\":' + '\'' + params.returns + '\''; formattedParams.push(returnParam); } - return "{" + formattedParams.join(',') + "}"; + return '{' + formattedParams.join(',') + '}'; } function getModuleName(state) { diff --git a/website/jsdocs/jsdocs.js b/website/jsdocs/jsdocs.js index 67f49238a3aa88..3f7cb953e1008e 100644 --- a/website/jsdocs/jsdocs.js +++ b/website/jsdocs/jsdocs.js @@ -8,7 +8,7 @@ */ /*jslint node: true */ -"use strict"; +'use strict'; var esprima = require('esprima-fb'); var fs = require('fs'); @@ -17,7 +17,7 @@ var Syntax = esprima.Syntax; var findExportDefinition = require('./findExportDefinition'); var genericTransform = require('./generic-function-visitor'); var genericVisitor = genericTransform.visitorList[0]; -var traverseFlat = require('./traverseFlat') +var traverseFlat = require('./traverseFlat'); var parseTypehint = require('./TypeExpressionParser').parse; // Don't save object properties source code that is longer than this @@ -76,7 +76,7 @@ function stripStaticUpstreamWarning(docblock) { return docblock; } // Esprima strips out the starting and ending tokens, so add them back - docblock = "/*" + docblock + "*/\n"; + docblock = '/*' + docblock + '*/\n'; return docblock; } @@ -104,13 +104,13 @@ function getFileDocBlock(commentsForFile) { var docblock; commentsForFile.some(function(comment, i) { if (comment.loc.start.line === 1) { - var lines = comment.value.split("\n"); + var lines = comment.value.split('\n'); var filteredLines = lines.filter(function(line) { var hasCopyright = !!line.match(/^\s*\*\s+Copyright/); var hasProvides = !!line.match(/^\s*\*\s+@provides/); return !hasCopyright && !hasProvides; }); - docblock = filteredLines.join("\n"); + docblock = filteredLines.join('\n'); return true; } }); @@ -149,7 +149,7 @@ function getDocBlock(node, commentsForFile, linesForFile) { var lineComment = commentsForFile[ii]; if (lineComment.loc.end.line === line) { docblock = '//' + lineComment.value + - (docblock ? "\n" + docblock : ""); + (docblock ? '\n' + docblock : ''); line--; } else { break; @@ -463,7 +463,7 @@ function getRequireData(node) { * @return {?object} data */ function parseSource(source) { - var lines = source.split("\n"); + var lines = source.split('\n'); var ast = esprima.parse(source, { loc: true, comment: true, diff --git a/website/jsdocs/meta.js b/website/jsdocs/meta.js index 399b297782a705..d61c5aaf131ce6 100644 --- a/website/jsdocs/meta.js +++ b/website/jsdocs/meta.js @@ -9,7 +9,7 @@ /*global exports:true*/ /*jslint node:true*/ -"use strict"; +'use strict'; var util = require('util'); diff --git a/website/jsdocs/traverseFlat.js b/website/jsdocs/traverseFlat.js index 8baa1b2e79d157..5f9ff14fb6422a 100644 --- a/website/jsdocs/traverseFlat.js +++ b/website/jsdocs/traverseFlat.js @@ -9,7 +9,7 @@ /*global exports:true*/ /*jslint node:true*/ -"use strict"; +'use strict'; var Syntax = require('esprima-fb').Syntax; diff --git a/website/jsdocs/type.js b/website/jsdocs/type.js index 612a1349de2c23..0ea74eb0c4bd0c 100644 --- a/website/jsdocs/type.js +++ b/website/jsdocs/type.js @@ -8,7 +8,7 @@ */ /*global exports:true*/ -"use strict"; +'use strict'; var util = require('util');