From 589f1e1850813cdb66f68df8da8d6ad4e8595427 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Mon, 27 Jun 2016 16:31:36 -0400 Subject: [PATCH 1/6] Implement TextInput onContentSizeChange on iOS --- Examples/UIExplorer/TextInputExample.ios.js | 15 +++++++++------ Libraries/Components/TextInput/TextInput.js | 12 ++++++++++++ Libraries/Text/RCTTextView.h | 1 + Libraries/Text/RCTTextView.m | 19 +++++++++++++++++++ Libraries/Text/RCTTextViewManager.m | 1 + 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index a3bae1d4629641..44a642ce08211f 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -102,18 +102,21 @@ class AutoExpandingTextInput extends React.Component { constructor(props) { super(props); - this.state = {text: '', height: 0}; + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and 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.', + height: 0, + }; } render() { return ( { - this.setState({ - text: event.nativeEvent.text, - height: event.nativeEvent.contentSize.height, - }); + onChangeText={(text) => { + this.setState({text}); + }} + onContentSizeChange={(contentSize) => { + this.setState({height: contentSize.height}); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} value={this.state.text} diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 505fdea088a8a6..e703acf012c050 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -310,6 +310,11 @@ const TextInput = React.createClass({ * Changed text is passed as an argument to the callback handler. */ onChangeText: PropTypes.func, + /** + * Callback this is called when the text input's content size changes. + * The new content size is passed as an argument to the callback. + */ + onContentSizeChange: PropTypes.func, /** * Callback that is called when text input ends. */ @@ -566,6 +571,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} + onContentSizeChange={this._onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} @@ -673,6 +679,12 @@ const TextInput = React.createClass({ } }, + _onContentSizeChange(event: Event) { + if (this.props.onContentSizeChange) { + this.props.onContentSizeChange(event.nativeEvent.contentSize); + } + }, + _onChange: function(event: Event) { // Make sure to fire the mostRecentEventCount first so it is already set on // native when the text value is set. diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index 8268e7a53ddea9..49e499fedccdf5 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -29,6 +29,7 @@ @property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, copy) RCTDirectEventBlock onChange; +@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange; @property (nonatomic, copy) RCTDirectEventBlock onSelectionChange; @property (nonatomic, copy) RCTDirectEventBlock onTextInput; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 94e99af4cc6001..035848698ffddf 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -77,6 +77,9 @@ @implementation RCTTextView BOOL _blockTextShouldChange; BOOL _nativeUpdatesInFlight; NSInteger _nativeEventCount; + + CGSize _previousContentSize; + BOOL _sendContentSizeUpdates; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -261,6 +264,17 @@ - (void)updateContentSize size.height = [_textView sizeThatFits:size].height; _scrollView.contentSize = size; _textView.frame = (CGRect){CGPointZero, size}; + + if (_sendContentSizeUpdates && _onContentSizeChange && !CGSizeEqualToSize(_previousContentSize, size)) { + _previousContentSize = size; + _onContentSizeChange(@{ + @"contentSize": @{ + @"height": @(size.height), + @"width": @(size.width), + }, + @"target": self.reactTag, + }); + } } - (void)updatePlaceholder @@ -633,6 +647,11 @@ - (BOOL)resignFirstResponder - (void)layoutSubviews { [super layoutSubviews]; + + // Start sending content size updates only after the view has been layed out + // otherwise we send multiple events with bad dimentions on initial render. + _sendContentSizeUpdates = YES; + [self updateFrames]; } diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index 97fbe5e351c5d4..cbbd60a38fa1eb 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -35,6 +35,7 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, textView.keyboardAppearance, UIKeyboardAppearance) RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) From 4da65ea3ef2ab8da3699ebbc3938bc36570a3589 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 7 Mar 2016 17:24:51 -0800 Subject: [PATCH 2/6] Implement TextInput onContentSizeChange on Android --- .../UIExplorer/TextInputExample.android.js | 23 ++++---- Libraries/Components/TextInput/TextInput.js | 13 ++++- .../uimanager/UIManagerModuleConstants.java | 9 +-- .../RecyclerViewBackedScrollViewManager.java | 3 - .../views/textinput/ContentSizeWatcher.java | 14 +++++ .../ReactContentSizeChangedEvent.java | 58 +++++++++++++++++++ .../react/views/textinput/ReactEditText.java | 25 +++++++- .../textinput/ReactTextInputManager.java | 56 +++++++++++++++++- 8 files changed, 178 insertions(+), 23 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js index 2238111cd07294..704597abcb4a23 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/TextInputExample.android.js @@ -76,18 +76,21 @@ var TextEventsExample = React.createClass({ class AutoExpandingTextInput extends React.Component { constructor(props) { super(props); - this.state = {text: '', height: 0}; + this.state = { + text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and 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.', + height: 0, + }; } render() { return ( { - this.setState({ - text: event.nativeEvent.text, - height: event.nativeEvent.contentSize.height, - }); + onContentSizeChange={(contentSize) => { + this.setState({height: contentSize.height}); + }} + onChangeText={(text) => { + this.setState({text}); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} value={this.state.text} @@ -412,19 +415,19 @@ exports.examples = [ render: function() { return ( - - - - diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index e703acf012c050..5bad9a75b67a74 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -528,6 +528,11 @@ const TextInput = React.createClass({ }; } + var onContentSizeChange; + if (this.props.onContentSizeChange) { + onContentSizeChange = this._onContentSizeChange; + } + var props = Object.assign({}, this.props); props.style = [styles.input, this.props.style]; if (!props.multiline) { @@ -571,7 +576,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} - onContentSizeChange={this._onContentSizeChange} + onContentSizeChange={onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} @@ -605,6 +610,11 @@ const TextInput = React.createClass({ }; } + var onContentSizeChange; + if (this.props.onContentSizeChange) { + onContentSizeChange = this._onContentSizeChange; + } + var autoCapitalize = UIManager.AndroidTextInput.Constants.AutoCapitalizationType[this.props.autoCapitalize]; var children = this.props.children; @@ -632,6 +642,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} + onContentSizeChange={onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onEndEditing={this.props.onEndEditing} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java index d3bdab1be52a73..7b5528bf20daee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java @@ -71,11 +71,12 @@ /* package */ static Map getDirectEventTypeConstants() { return MapBuilder.builder() - .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange")) - .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart")) - .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish")) - .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError")) + .put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange")) .put("topLayout", MapBuilder.of("registrationName", "onLayout")) + .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError")) + .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish")) + .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart")) + .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange")) .build(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java index dfac0da4e10585..b82c03f74734e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java @@ -85,9 +85,6 @@ public void scrollTo( Map getExportedCustomDirectEventTypeConstants() { return MapBuilder.builder() .put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll")) - .put( - ContentSizeChangeEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onContentSizeChange")) .build(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java new file mode 100644 index 00000000000000..fab6bbc1e34d1a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java @@ -0,0 +1,14 @@ +/** + * 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. + */ + +package com.facebook.react.views.textinput; + +public interface ContentSizeWatcher { + public void onLayout(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java new file mode 100644 index 00000000000000..a5d0ef21936f32 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java @@ -0,0 +1,58 @@ +/** + * 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. + */ + +package com.facebook.react.views.textinput; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Event emitted by EditText native view when content size changes. + */ +public class ReactContentSizeChangedEvent extends Event { + + public static final String EVENT_NAME = "topContentSizeChange"; + + private int mContentWidth; + private int mContentHeight; + + public ReactContentSizeChangedEvent( + int viewId, + long timestampMs, + int contentSizeWidth, + int contentSizeHeight) { + super(viewId, timestampMs); + mContentWidth = contentSizeWidth; + mContentHeight = contentSizeHeight; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + WritableMap eventData = Arguments.createMap(); + + WritableMap contentSize = Arguments.createMap(); + contentSize.putDouble("width", mContentWidth); + contentSize.putDouble("height", mContentHeight); + eventData.putMap("contentSize", contentSize); + + eventData.putInt("target", getViewTag()); + return eventData; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 7f9a13d7d27b7a..907ef31e393ad0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -71,6 +71,7 @@ public class ReactEditText extends EditText { private boolean mContainsImages; private boolean mBlurOnSubmit; private @Nullable SelectionWatcher mSelectionWatcher; + private @Nullable ContentSizeWatcher mContentSizeWatcher; private final InternalKeyListener mKeyListener; private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard(); @@ -102,7 +103,23 @@ public ReactEditText(Context context) { // TODO: t6408636 verify if we should schedule a layout after a View does a requestLayout() @Override public boolean isLayoutRequested() { - return false; + // If we are watching and updating container height based on content size + // then we don't want to scroll right away. This isn't perfect -- you might + // want to limit the height the text input can grow to. Possible solution + // is to add another prop that determines whether we should scroll to end + // of text. + if (mContentSizeWatcher != null) { + return true; + } else { + return false; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mContentSizeWatcher != null) { + mContentSizeWatcher.onLayout(); + } } // Consume 'Enter' key events: TextView tries to give focus to the next TextInput, but it can't @@ -162,6 +179,10 @@ public void removeTextChangedListener(TextWatcher watcher) { } } + public void setContentSizeWatcher(ContentSizeWatcher contentSizeWatcher) { + mContentSizeWatcher = contentSizeWatcher; + } + @Override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); @@ -447,7 +468,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { if (!mIsSettingTextFromJS && mListeners != null) { - for (android.text.TextWatcher listener : mListeners) { + for (TextWatcher listener : mListeners) { listener.afterTextChanged(s); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index d8d835dab33b81..6aa33d99ed8ab3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -66,7 +66,7 @@ public class ReactTextInputManager extends BaseViewManager Date: Mon, 27 Jun 2016 19:32:48 -0400 Subject: [PATCH 3/6] Fix typo --- Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 5bad9a75b67a74..3188544aacb32b 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -311,7 +311,7 @@ const TextInput = React.createClass({ */ onChangeText: PropTypes.func, /** - * Callback this is called when the text input's content size changes. + * Callback that is called when the text input's content size changes. * The new content size is passed as an argument to the callback. */ onContentSizeChange: PropTypes.func, From 44004dec943baac0d416d4114c2c124790b2301a Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 28 Jun 2016 02:01:32 -0400 Subject: [PATCH 4/6] Fix typos take 2 --- Libraries/Text/RCTTextView.m | 6 +++--- .../react/views/textinput/ReactTextInputManager.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 035848698ffddf..1d1415a0fed637 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -648,10 +648,10 @@ - (void)layoutSubviews { [super layoutSubviews]; - // Start sending content size updates only after the view has been layed out - // otherwise we send multiple events with bad dimentions on initial render. + // Start sending content size updates only after the view has been laid out + // otherwise we send multiple events with bad dimensions on initial render. _sendContentSizeUpdates = YES; - + [self updateFrames]; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 6aa33d99ed8ab3..951f1c6f6190ca 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -245,7 +245,7 @@ public void setBlurOnSubmit(ReactEditText view, boolean blurOnSubmit) { } @ReactProp(name = "onContentSizeChange", defaultBoolean = false) - public void setOnChangecontentSize(final ReactEditText view, boolean onContentSizeChange) { + public void setOnContentSizeChange(final ReactEditText view, boolean onContentSizeChange) { if (onContentSizeChange) { view.setContentSizeWatcher(new ReactContentSizeWatcher(view)); } else { From a7f28dd14df5db919cfc55f005a40102a862ec1b Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Mon, 4 Jul 2016 15:16:09 -0400 Subject: [PATCH 5/6] Make sure onContentSizeChange is only called for multiline inputs --- Libraries/Components/TextInput/TextInput.js | 4 +++- Libraries/Text/RCTTextView.m | 6 +++--- .../facebook/react/views/textinput/ReactEditText.java | 11 +++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 3188544aacb32b..a849984acc27cb 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -270,7 +270,7 @@ const TextInput = React.createClass({ * Sets the return key to the label. Use it instead of `returnKeyType`. * @platform android */ - returnKeyLabel: PropTypes.string, + returnKeyLabel: PropTypes.string, /** * Limits the maximum number of characters that can be entered. Use this * instead of implementing the logic in JS to avoid flicker. @@ -313,6 +313,8 @@ const TextInput = React.createClass({ /** * Callback that is called when the text input's content size changes. * The new content size is passed as an argument to the callback. + * + * Only called for multiline text inputs. */ onContentSizeChange: PropTypes.func, /** diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 1d1415a0fed637..4c377a58bcc11b 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -79,7 +79,7 @@ @implementation RCTTextView NSInteger _nativeEventCount; CGSize _previousContentSize; - BOOL _sendContentSizeUpdates; + BOOL _viewDidCompleteInitialLayout; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -265,7 +265,7 @@ - (void)updateContentSize _scrollView.contentSize = size; _textView.frame = (CGRect){CGPointZero, size}; - if (_sendContentSizeUpdates && _onContentSizeChange && !CGSizeEqualToSize(_previousContentSize, size)) { + if (_viewDidCompleteInitialLayout && _onContentSizeChange && !CGSizeEqualToSize(_previousContentSize, size)) { _previousContentSize = size; _onContentSizeChange(@{ @"contentSize": @{ @@ -650,7 +650,7 @@ - (void)layoutSubviews // Start sending content size updates only after the view has been laid out // otherwise we send multiple events with bad dimensions on initial render. - _sendContentSizeUpdates = YES; + _viewDidCompleteInitialLayout = YES; [self updateFrames]; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 907ef31e393ad0..d5f9c604229a8a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -109,7 +109,7 @@ public boolean isLayoutRequested() { // is to add another prop that determines whether we should scroll to end // of text. if (mContentSizeWatcher != null) { - return true; + return isMultiline(); } else { return false; } @@ -126,8 +126,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto // since we only allow JS to change focus, which in turn causes TextView to crash. @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER && - ((getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0 )) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !isMultiline()) { hideSoftKeyboard(); return true; } @@ -233,7 +232,7 @@ public void setInputType(int type) { mStagedInputType = type; // Input type password defaults to monospace font, so we need to re-apply the font super.setTypeface(tf); - + // We override the KeyListener so that all keys on the soft input keyboard as well as hardware // keyboards work. Some KeyListeners like DigitsKeyListener will display the keyboard but not // accept all input from it @@ -350,6 +349,10 @@ private TextWatcherDelegator getTextWatcherDelegator() { return mTextWatcherDelegator; } + private boolean isMultiline() { + return (getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0; + } + /* package */ void setGravityHorizontal(int gravityHorizontal) { if (gravityHorizontal == 0) { gravityHorizontal = mDefaultGravityHorizontal; From ee6dcf6f13db252de2faf2b2a638d6314b7354a7 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 6 Jul 2016 02:37:27 -0400 Subject: [PATCH 6/6] Pass event instead of content size --- .../UIExplorer/TextInputExample.android.js | 4 ++-- Examples/UIExplorer/TextInputExample.ios.js | 4 ++-- Libraries/Components/TextInput/TextInput.js | 23 ++++--------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js index 704597abcb4a23..7dff92b8e3a6c7 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/TextInputExample.android.js @@ -86,8 +86,8 @@ class AutoExpandingTextInput extends React.Component { { - this.setState({height: contentSize.height}); + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); }} onChangeText={(text) => { this.setState({text}); diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 44a642ce08211f..bebce92032d962 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -115,8 +115,8 @@ class AutoExpandingTextInput extends React.Component { onChangeText={(text) => { this.setState({text}); }} - onContentSizeChange={(contentSize) => { - this.setState({height: contentSize.height}); + onContentSizeChange={(event) => { + this.setState({height: event.nativeEvent.contentSize.height}); }} style={[styles.default, {height: Math.max(35, this.state.height)}]} value={this.state.text} diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index a849984acc27cb..81e39d0167c454 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -312,7 +312,8 @@ const TextInput = React.createClass({ onChangeText: PropTypes.func, /** * Callback that is called when the text input's content size changes. - * The new content size is passed as an argument to the callback. + * This will be called with + * `{ nativeEvent: { contentSize: { width, height } } }`. * * Only called for multiline text inputs. */ @@ -530,11 +531,6 @@ const TextInput = React.createClass({ }; } - var onContentSizeChange; - if (this.props.onContentSizeChange) { - onContentSizeChange = this._onContentSizeChange; - } - var props = Object.assign({}, this.props); props.style = [styles.input, this.props.style]; if (!props.multiline) { @@ -578,7 +574,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} - onContentSizeChange={onContentSizeChange} + onContentSizeChange={this.props.onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} @@ -612,11 +608,6 @@ const TextInput = React.createClass({ }; } - var onContentSizeChange; - if (this.props.onContentSizeChange) { - onContentSizeChange = this._onContentSizeChange; - } - var autoCapitalize = UIManager.AndroidTextInput.Constants.AutoCapitalizationType[this.props.autoCapitalize]; var children = this.props.children; @@ -644,7 +635,7 @@ const TextInput = React.createClass({ onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} - onContentSizeChange={onContentSizeChange} + onContentSizeChange={this.props.onContentSizeChange} onSelectionChange={onSelectionChange} onTextInput={this._onTextInput} onEndEditing={this.props.onEndEditing} @@ -692,12 +683,6 @@ const TextInput = React.createClass({ } }, - _onContentSizeChange(event: Event) { - if (this.props.onContentSizeChange) { - this.props.onContentSizeChange(event.nativeEvent.contentSize); - } - }, - _onChange: function(event: Event) { // Make sure to fire the mostRecentEventCount first so it is already set on // native when the text value is set.