Skip to content

Commit

Permalink
Implements onKeyPress
Browse files Browse the repository at this point in the history
Summary: - When a key is pressed, it's `key value` is passed as an argument to the callback handler.
 - For `Enter` and `Backspace` keys, I'm using their `key value` as defined [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#Key_values). As per JonasJonny & brentvatne's [suggestion](#1882 (comment)).

- Example
```javascript
 _handleKeyPress: function(e) {
      console.log(e.nativeEvent.key);
  },

  render: function() {
    return (
      <View style={styles.container}>
        <TextInput
            style={{width: 150, height: 25, borderWidth: 0.5}}
            onKeyPress={this._handleKeyPress}
        />
        <TextInput
            style={{width: 150, height: 100, borderWidth: 0.5}}
            onKeyPress={this._handleKeyPress}
            multiline={true}
        />
      </View>
    );
  }
```
- Implements [shouldChangeCharactersInRange](https://developer.apple.com/library/prerelease/ios/documentat
Closes #2082

Reviewed By: javache

Differential Revision: D2280460

Pulled By: nicklockwood

fb-gh-sync-id: 1f824f80649043dc2520c089e2531d428d799405
  • Loading branch information
dsibiski authored and facebook-github-bot-6 committed Nov 2, 2015
1 parent 6539b26 commit 6c7c845
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 12 deletions.
8 changes: 7 additions & 1 deletion Examples/UIExplorer/TextInputExample.ios.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var TextEventsExample = React.createClass({
curText: '<No Event>', curText: '<No Event>',
prevText: '<No Event>', prevText: '<No Event>',
prev2Text: '<No Event>', prev2Text: '<No Event>',
prev3Text: '<No Event>',
}; };
}, },


Expand All @@ -51,6 +52,7 @@ var TextEventsExample = React.createClass({
curText: text, curText: text,
prevText: state.curText, prevText: state.curText,
prev2Text: state.prevText, prev2Text: state.prevText,
prev3Text: state.prev2Text,
}; };
}); });
}, },
Expand All @@ -73,12 +75,16 @@ var TextEventsExample = React.createClass({
onSubmitEditing={(event) => this.updateText( onSubmitEditing={(event) => this.updateText(
'onSubmitEditing text: ' + event.nativeEvent.text 'onSubmitEditing text: ' + event.nativeEvent.text
)} )}
onKeyPress={(event) => {
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
}}
style={styles.default} style={styles.default}
/> />
<Text style={styles.eventLabel}> <Text style={styles.eventLabel}>
{this.state.curText}{'\n'} {this.state.curText}{'\n'}
(prev: {this.state.prevText}){'\n'} (prev: {this.state.prevText}){'\n'}
(prev2: {this.state.prev2Text}) (prev2: {this.state.prev2Text}){'\n'}
(prev3: {this.state.prev3Text})
</Text> </Text>
</View> </View>
); );
Expand Down
7 changes: 7 additions & 0 deletions Libraries/Components/TextInput/TextInput.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ var TextInput = React.createClass({
* Callback that is called when the text input's submit button is pressed. * Callback that is called when the text input's submit button is pressed.
*/ */
onSubmitEditing: PropTypes.func, onSubmitEditing: PropTypes.func,
/**
* Callback that is called when a key is pressed.
* Pressed key value is passed as an argument to the callback handler.
* Fires before onChange callbacks.
* @platform ios
*/
onKeyPress: PropTypes.func,
/** /**
* Invoked on mount and layout changes with `{x, y, width, height}`. * Invoked on mount and layout changes with `{x, y, width, height}`.
*/ */
Expand Down
3 changes: 3 additions & 0 deletions Libraries/Text/RCTTextField.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
@property (nonatomic, strong) UIColor *placeholderTextColor; @property (nonatomic, strong) UIColor *placeholderTextColor;
@property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, strong) NSNumber *maxLength;
@property (nonatomic, assign) BOOL textWasPasted;


- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

- (void)textFieldDidChange; - (void)textFieldDidChange;
- (void)sendKeyValueForString:(NSString *)string;


@end @end
22 changes: 22 additions & 0 deletions Libraries/Text/RCTTextField.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)


- (void)sendKeyValueForString:(NSString *)string
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
reactTag:self.reactTag
text:nil
key:string
eventCount:_nativeEventCount];
}

// This method is overriden for `onKeyPress`. The manager
// will not send a keyPress for text that was pasted.
- (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
}

- (void)setText:(NSString *)text - (void)setText:(NSString *)text
{ {
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount; NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
Expand Down Expand Up @@ -134,6 +151,7 @@ - (void)textFieldDidChange
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag reactTag:self.reactTag
text:self.text text:self.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }


Expand All @@ -142,13 +160,15 @@ - (void)textFieldEndEditing
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag reactTag:self.reactTag
text:self.text text:self.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }
- (void)textFieldSubmitEditing - (void)textFieldSubmitEditing
{ {
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
reactTag:self.reactTag reactTag:self.reactTag
text:self.text text:self.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }


Expand All @@ -162,6 +182,7 @@ - (void)textFieldBeginEditing
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag reactTag:self.reactTag
text:self.text text:self.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }


Expand All @@ -181,6 +202,7 @@ - (BOOL)resignFirstResponder
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag reactTag:self.reactTag
text:self.text text:self.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }
return result; return result;
Expand Down
15 changes: 15 additions & 0 deletions Libraries/Text/RCTTextFieldManager.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ - (UIView *)view


- (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string - (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{ {
// Only allow single keypresses for onKeyPress, pasted text will not be sent.
if (textField.textWasPasted) {
textField.textWasPasted = NO;
} else {
[textField sendKeyValueForString:string];
}

if (textField.maxLength == nil || [string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return if (textField.maxLength == nil || [string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return
return YES; return YES;
} }
Expand All @@ -54,6 +61,14 @@ - (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRan
} }
} }


// This method allows us to detect a `Backspace` keyPress
// even when there is no more text in the TextField
- (BOOL)keyboardInputShouldDelete:(RCTTextField *)textField
{
[self textField:textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
return YES;
}

RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL) RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL) RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL)
Expand Down
40 changes: 35 additions & 5 deletions Libraries/Text/RCTTextView.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@
#import "RCTUtils.h" #import "RCTUtils.h"
#import "UIView+React.h" #import "UIView+React.h"


@interface RCTUITextView : UITextView

@property (nonatomic, assign) BOOL textWasPasted;

@end

@implementation RCTUITextView

- (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
}

@end

@implementation RCTTextView @implementation RCTTextView
{ {
RCTEventDispatcher *_eventDispatcher; RCTEventDispatcher *_eventDispatcher;
Expand All @@ -33,7 +49,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
_eventDispatcher = eventDispatcher; _eventDispatcher = eventDispatcher;
_placeholderTextColor = [self defaultPlaceholderTextColor]; _placeholderTextColor = [self defaultPlaceholderTextColor];


_textView = [[UITextView alloc] initWithFrame:self.bounds]; _textView = [[RCTUITextView alloc] initWithFrame:self.bounds];
_textView.backgroundColor = [UIColor clearColor]; _textView.backgroundColor = [UIColor clearColor];
_textView.scrollsToTop = NO; _textView.scrollsToTop = NO;
_textView.delegate = self; _textView.delegate = self;
Expand All @@ -56,15 +72,15 @@ - (void)updateFrames
// first focused. // first focused.
UIEdgeInsets adjustedFrameInset = UIEdgeInsetsZero; UIEdgeInsets adjustedFrameInset = UIEdgeInsetsZero;
adjustedFrameInset.left = _contentInset.left - 5; adjustedFrameInset.left = _contentInset.left - 5;

UIEdgeInsets adjustedTextContainerInset = _contentInset; UIEdgeInsets adjustedTextContainerInset = _contentInset;
adjustedTextContainerInset.top += 5; adjustedTextContainerInset.top += 5;
adjustedTextContainerInset.left = 0; adjustedTextContainerInset.left = 0;

CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset); CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset);
_textView.frame = frame; _textView.frame = frame;
_placeholderView.frame = frame; _placeholderView.frame = frame;

_textView.textContainerInset = adjustedTextContainerInset; _textView.textContainerInset = adjustedTextContainerInset;
_placeholderView.textContainerInset = adjustedTextContainerInset; _placeholderView.textContainerInset = adjustedTextContainerInset;
} }
Expand Down Expand Up @@ -138,8 +154,18 @@ - (NSString *)text
return _textView.text; return _textView.text;
} }


- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text - (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{ {
if (textView.textWasPasted) {
textView.textWasPasted = NO;
} else {
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
reactTag:self.reactTag
text:nil
key:text
eventCount:_nativeEventCount];
}

if (_maxLength == nil) { if (_maxLength == nil) {
return YES; return YES;
} }
Expand Down Expand Up @@ -215,6 +241,7 @@ - (void)textViewDidBeginEditing:(UITextView *)textView
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag reactTag:self.reactTag
text:textView.text text:textView.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }


Expand All @@ -225,6 +252,7 @@ - (void)textViewDidChange:(UITextView *)textView
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag reactTag:self.reactTag
text:textView.text text:textView.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];


} }
Expand All @@ -234,6 +262,7 @@ - (void)textViewDidEndEditing:(UITextView *)textView
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
reactTag:self.reactTag reactTag:self.reactTag
text:textView.text text:textView.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }


Expand All @@ -253,6 +282,7 @@ - (BOOL)resignFirstResponder
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag reactTag:self.reactTag
text:_textView.text text:_textView.text
key:nil
eventCount:_nativeEventCount]; eventCount:_nativeEventCount];
} }
return result; return result;
Expand Down
4 changes: 3 additions & 1 deletion React/Base/RCTEventDispatcher.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeBlur, RCTTextEventTypeBlur,
RCTTextEventTypeChange, RCTTextEventTypeChange,
RCTTextEventTypeSubmit, RCTTextEventTypeSubmit,
RCTTextEventTypeEnd RCTTextEventTypeEnd,
RCTTextEventTypeKeyPress
}; };


typedef NS_ENUM(NSInteger, RCTScrollEventType) { typedef NS_ENUM(NSInteger, RCTScrollEventType) {
Expand Down Expand Up @@ -95,6 +96,7 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
- (void)sendTextEventWithType:(RCTTextEventType)type - (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag reactTag:(NSNumber *)reactTag
text:(NSString *)text text:(NSString *)text
key:(NSString *)key
eventCount:(NSInteger)eventCount; eventCount:(NSInteger)eventCount;


/** /**
Expand Down
31 changes: 26 additions & 5 deletions React/Base/RCTEventDispatcher.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ - (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
- (void)sendTextEventWithType:(RCTTextEventType)type - (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag reactTag:(NSNumber *)reactTag
text:(NSString *)text text:(NSString *)text
key:(NSString *)key
eventCount:(NSInteger)eventCount eventCount:(NSInteger)eventCount
{ {
static NSString *events[] = { static NSString *events[] = {
Expand All @@ -152,16 +153,36 @@ - (void)sendTextEventWithType:(RCTTextEventType)type
@"change", @"change",
@"submitEditing", @"submitEditing",
@"endEditing", @"endEditing",
@"keyPress"
}; };


[self sendInputEventWithName:events[type] body:text ? @{ NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithDictionary:@{
@"text": text,
@"eventCount": @(eventCount),
@"target": reactTag
} : @{
@"eventCount": @(eventCount), @"eventCount": @(eventCount),
@"target": reactTag @"target": reactTag
}]; }];

if (text) {
body[@"text"] = text;
}

if (key) {
if (key.length == 0) {
key = @"Backspace"; // backspace
} else {
switch ([key characterAtIndex:0]) {
case '\t':
key = @"Tab";
break;
case '\n':
key = @"Enter";
default:
break;
}
}
body[@"key"] = key;
}

[self sendInputEventWithName:events[type] body:body];
} }


- (void)sendEvent:(id<RCTEvent>)event - (void)sendEvent:(id<RCTEvent>)event
Expand Down
1 change: 1 addition & 0 deletions React/Views/RCTViewManager.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ - (NSArray *)customBubblingEventTypes
@"blur", @"blur",
@"submitEditing", @"submitEditing",
@"endEditing", @"endEditing",
@"keyPress",


// Touch events // Touch events
@"touchStart", @"touchStart",
Expand Down

0 comments on commit 6c7c845

Please sign in to comment.