From 2cc0a2e76e4206eea56e6b7063a4d05ed027fef4 Mon Sep 17 00:00:00 2001 From: Adam Gleitman Date: Mon, 19 Aug 2024 15:38:50 -0700 Subject: [PATCH] [0.74-stable] Make TextInput events more reliable (#2159 + #2160) (#2168) * Deduplicate `textInputDid(Begin|End)Editing` calls for multiline `` elements (#2159) * Add _isCurrentlyEditing to RCTBaseTextInputView * Move _isCurrentlyEditing check earlier in textInputDidBeginEditing * Adjust comment * nit: remove extra newline * Limit changes to macOS --------- Co-authored-by: Adam Gleitman * Prevent spurious onBlur/onEndEditing events in `` elements with placeholder text (#2160) * Add _isUpdatingPlaceholderText to prevent spurious "did end editing" notifications * Organize macOS tags and ifdef blocks --------- Co-authored-by: Adam Gleitman --------- Co-authored-by: Adam Gleitman --- .../Text/TextInput/RCTBaseTextInputView.mm | 20 +++++++++++++++++++ .../TextInput/Singleline/RCTUITextField.mm | 20 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index a33d47f7472f94..1e6059ef6a18b7 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -36,6 +36,9 @@ @implementation RCTBaseTextInputView { BOOL _hasInputAccessoryView; // [macOS] remove explicit _predictedText ivar declaration BOOL _didMoveToWindow; +#if TARGET_OS_OSX // [macOS avoids duplicating effects of textInputDid(Begin|End)Editing calls + BOOL _isCurrentlyEditing; +#endif // macOS] } #if !TARGET_OS_OSX // [macOS] @@ -71,6 +74,9 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge if (self = [super initWithEventDispatcher:bridge.eventDispatcher]) { // [macOS] _bridge = bridge; _eventDispatcher = bridge.eventDispatcher; +#if TARGET_OS_OSX // [macOS + _isCurrentlyEditing = NO; +#endif // macOS] } return self; @@ -446,6 +452,13 @@ - (BOOL)textInputShouldBeginEditing - (void)textInputDidBeginEditing { +#if TARGET_OS_OSX // [macOS consolidate duplicate callbacks + if (_isCurrentlyEditing) { + return; + } + _isCurrentlyEditing = YES; +#endif // macOS] + if (_clearTextOnFocus) { self.backedTextInputView.attributedText = [NSAttributedString new]; } @@ -474,6 +487,13 @@ - (BOOL)textInputShouldEndEditing - (void)textInputDidEndEditing { +#if TARGET_OS_OSX // [macOS consolidate duplicate callbacks + if (!_isCurrentlyEditing) { + return; + } + _isCurrentlyEditing = NO; +#endif // macOS] + self.ghostText = nil; // [macOS] [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index 0f1bdaaa61eb29..32506b174168a4 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -93,6 +93,9 @@ @implementation RCTUITextField { #endif RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter; NSDictionary *_defaultTextAttributes; +#if TARGET_OS_OSX // [macOS + BOOL _isUpdatingPlaceholderText; +#endif // macOS] } #if TARGET_OS_OSX // [macOS @@ -116,6 +119,9 @@ - (instancetype)initWithFrame:(CGRect)frame _textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self]; _scrollEnabled = YES; +#if TARGET_OS_OSX // [macOS + _isUpdatingPlaceholderText = NO; +#endif // macOS] } return self; @@ -361,8 +367,11 @@ - (void)_updatePlaceholder self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder ?: @"" attributes:[self _placeholderTextAttributes]]; #else // [macOS + // Set _isUpdatingPlaceholderText to manually suppress RCTUITextFieldDelegate's textFieldEndEditing + _isUpdatingPlaceholderText = YES; self.placeholderAttributedString = [[NSAttributedString alloc] initWithString:self.placeholder ?: @"" attributes:[self _placeholderTextAttributes]]; + _isUpdatingPlaceholderText = NO; #endif // macOS] } @@ -485,10 +494,17 @@ - (void)textDidChange:(NSNotification *)notification [delegate textFieldDidChange:self]; } } - + - (void)textDidEndEditing:(NSNotification *)notification { - [super textDidEndEditing:notification]; + [super textDidEndEditing:notification]; + + // On macOS, setting placeholderAttributedString causes AppKit to call textDidEndEditing. + // We don't want this to propagate or else we get unexpected onBlur/onEndEditing events. + if (_isUpdatingPlaceholderText) { + return; + } + id delegate = self.delegate; if ([delegate respondsToSelector:@selector(textFieldEndEditing:)]) { [delegate textFieldEndEditing:self];