From f75a753e553a7cb11ed431a19a6e401e804cd88f Mon Sep 17 00:00:00 2001 From: Tom Underhill Date: Tue, 12 Mar 2019 10:32:57 -0700 Subject: [PATCH] Fix so that onfocus events are fired on single and multiline TextInput components. --- .../Text/TextInput/Multiline/RCTUITextView.m | 11 +- .../TextInput/Singleline/RCTUITextField.m | 18 +- RNTester/js/FocusEventsExample.js | 176 +++++++++++++++--- 3 files changed, 171 insertions(+), 34 deletions(-) diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 3379f369a62afe..8d51db10f0f579 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -145,7 +145,16 @@ - (NSAttributedString*)attributedText - (BOOL)becomeFirstResponder { - return [self.window makeFirstResponder:self]; + BOOL success = [[self window] makeFirstResponder:self]; + + if (success) { + id textInputDelegate = [self textInputDelegate]; + if ([textInputDelegate respondsToSelector:@selector(textInputDidBeginEditing)]) { + [textInputDelegate textInputDidBeginEditing]; + } + } + + return success; } #endif // ]TODO(macOS ISS#2323203) diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.m b/Libraries/Text/TextInput/Singleline/RCTUITextField.m index 4cd470b0d445b9..23e0039ce7f7fc 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.m +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.m @@ -305,15 +305,6 @@ - (CGRect)editingRectForBounds:(CGRect)bounds #pragma mark - NSTextViewDelegate methods -- (void)textDidBeginEditing:(NSNotification *)notification -{ - [super textDidBeginEditing:notification]; - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textFieldBeginEditing:)]) { - [delegate textFieldBeginEditing:self]; - } -} - - (void)textDidChange:(NSNotification *)notification { [super textDidChange:notification]; @@ -358,6 +349,15 @@ - (BOOL)becomeFirstResponder { BOOL isFirstResponder = [super becomeFirstResponder]; if (isFirstResponder) { + id delegate = self.delegate; + if ([delegate respondsToSelector:@selector(textFieldBeginEditing:)]) { + // The AppKit -[NSTextField textDidBeginEditing:] notification is only called when the user + // makes the first change to the text in the text field. + // The react-native -[RCTUITextFieldDelegate textFieldBeginEditing:] is intended to be + // called when the text field is focused so call it here in becomeFirstResponder. + [delegate textFieldBeginEditing:self]; + } + NSScrollView *scrollView = [self enclosingScrollView]; if (scrollView != nil) { NSRect visibleRect = [[scrollView documentView] convertRect:self.frame fromView:self]; diff --git a/RNTester/js/FocusEventsExample.js b/RNTester/js/FocusEventsExample.js index c15656d3b2b38e..757ae81b26bc58 100644 --- a/RNTester/js/FocusEventsExample.js +++ b/RNTester/js/FocusEventsExample.js @@ -16,7 +16,7 @@ var Platform = require('Platform'); var {StyleSheet, Text, View, TextInput} = ReactNative; type State = { - eventStream: string; + eventStream: string, }; class FocusEventExample extends React.Component<{}, State> { @@ -28,38 +28,158 @@ class FocusEventExample extends React.Component<{}, State> { return ( - Focus events are called when a component receives or loses focus. - This can be acquired by manually focusing components {Platform.OS === 'macos' ? ' or using tab-based nav' : '' } + Focus events are called when a component receives or loses focus. This + can be acquired by manually focusing components + {Platform.OS === 'macos' ? ' or using tab-based nav' : ''} { - this.setState((prevState) => ({ eventStream: prevState.eventStream + '\nTextInput Focus' })); + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nTextInput Focus', + })); }} onBlur={() => { - this.setState((prevState) => ({ eventStream: prevState.eventStream + '\nTextInput Blur' })); + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nTextInput Blur', + })); }} - style={styles.default} + placeholder={'TextInput'} + placeholderTextColor={ + Platform.OS === 'macos' ? {semantic: 'textColor'} : 'black' + } + style={styles.textInput} /> - + {// Only test View on MacOS, since canBecomeFirstResponder is false on all iOS, therefore we can't focus - Platform.OS === 'macos' ? - { - this.setState((prevState) => ({ eventStream: prevState.eventStream + '\nView Focus' })); + Platform.OS === 'macos' ? ( + { + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nView Focus', + })); + }} + onBlur={() => { + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nView Blur', + })); + }}> + Focusable View + + ) : null} + + { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Singleline TextInput Parent Focus', + })); + }} + onBlur={() => { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Singleline TextInput Parent Blur', + })); + }}> + { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Singleline TextInput Focus', + })); }} onBlur={() => { - this.setState((prevState) => ({ eventStream: prevState.eventStream + '\nView Blur' })); + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Singleline TextInput Blur', + })); + }} + style={styles.textInput} + placeholder={'Nested Singleline TextInput'} + placeholderTextColor={ + Platform.OS === 'macos' ? {semantic: 'textColor'} : 'black' + } + /> + + + {// Only test View on MacOS, since canBecomeFirstResponder is false on all iOS, therefore we can't focus + Platform.OS === 'macos' ? ( + { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + '\nNested View Parent Focus', + })); }} - > - - Focusable View - + onBlur={() => { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + '\nNested View Parent Blur', + })); + }}> + { + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nNested View Focus', + })); + }} + onBlur={() => { + this.setState(prevState => ({ + eventStream: prevState.eventStream + '\nNested View Blur', + })); + }}> + Nested Focusable View + - : null} - - {'Events: ' + this.state.eventStream + '\n\n'} - + ) : null} + + { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Multiline TextInput Parent Focus', + })); + }} + onBlur={() => { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Multiline TextInput Parent Blur', + })); + }}> + { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + + '\nNested Multiline TextInput Focus', + })); + }} + onBlur={() => { + this.setState(prevState => ({ + eventStream: + prevState.eventStream + '\nNested Multiline TextInput Blur', + })); + }} + style={styles.textInput} + multiline={true} + placeholder={'Nested Multiline TextInput'} + placeholderTextColor={ + Platform.OS === 'macos' ? {semantic: 'textColor'} : 'black' + } + /> + + + {'Events: ' + this.state.eventStream + '\n\n'} ); @@ -67,9 +187,18 @@ class FocusEventExample extends React.Component<{}, State> { } var styles = StyleSheet.create({ - default: { + textInput: { + ...Platform.select({ + macos: { + color: {semantic: 'textColor'}, + backgroundColor: {semantic: 'textBackgroundColor'}, + borderColor: {semantic: 'gridColor'}, + }, + default: { + borderColor: '#0f0f0f', + }, + }), borderWidth: StyleSheet.hairlineWidth, - borderColor: '#0f0f0f', flex: 1, fontSize: 13, padding: 4, @@ -77,8 +206,7 @@ var styles = StyleSheet.create({ }); exports.title = 'Focus Events'; -exports.description = - 'Examples that show how Focus events can be used.'; +exports.description = 'Examples that show how Focus events can be used.'; exports.examples = [ { title: 'FocusEventExample',