Implements `onKeyPress`
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](https://github.com/facebook/react-native/issues/1882#issuecomment-123485883). - 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 https://github.com/facebook/react-native/pull/2082 Reviewed By: javache Differential Revision: D2280460 Pulled By: nicklockwood fb-gh-sync-id: 1f824f80649043dc2520c089e2531d428d799405
This commit is contained in:
parent
6539b26810
commit
6c7c845145
|
@ -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>',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}`.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -39,6 +39,23 @@
|
||||||
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;
|
||||||
|
@ -134,6 +151,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:self.text
|
text:self.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +160,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
[_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
|
||||||
|
@ -149,6 +168,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:self.text
|
text:self.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +182,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:self.text
|
text:self.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +202,7 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
[_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;
|
||||||
|
|
|
@ -31,6 +31,13 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
- (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;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +61,14 @@ RCT_EXPORT_MODULE()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -33,7 +49,7 @@
|
||||||
_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;
|
||||||
|
@ -56,15 +72,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
@ -138,8 +154,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -215,6 +241,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:textView.text
|
text:textView.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +252,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:textView.text
|
text:textView.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -234,6 +262,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:textView.text
|
text:textView.text
|
||||||
|
key:nil
|
||||||
eventCount:_nativeEventCount];
|
eventCount:_nativeEventCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +282,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||||
[_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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -144,6 +144,7 @@ RCT_EXPORT_MODULE()
|
||||||
- (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[] = {
|
||||||
|
@ -152,16 +153,36 @@ RCT_EXPORT_MODULE()
|
||||||
@"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
|
||||||
|
|
|
@ -80,6 +80,7 @@ RCT_EXPORT_MODULE()
|
||||||
@"blur",
|
@"blur",
|
||||||
@"submitEditing",
|
@"submitEditing",
|
||||||
@"endEditing",
|
@"endEditing",
|
||||||
|
@"keyPress",
|
||||||
|
|
||||||
// Touch events
|
// Touch events
|
||||||
@"touchStart",
|
@"touchStart",
|
||||||
|
|
Loading…
Reference in New Issue