From 85739c7f7b77caa5452d1c1f99e46c75044e1b54 Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Tue, 2 Jun 2026 14:37:18 -0400 Subject: [PATCH] Fixes multiline text input multiline focus ring and input focus handling --- .../Text/TextInput/Multiline/RCTUITextView.mm | 17 +++++++++++++++++ .../TextInput/Multiline/RCTWrappedTextView.m | 10 ++++++++++ .../TextInput/RCTTextInputComponentView.mm | 14 +++++++++++--- .../TextInput/RCTTextInputUtils.mm | 11 +++++++++-- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm index f7747ad8fd12..9cf1c48d9fdb 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm @@ -26,6 +26,7 @@ @implementation RCTUITextView { #endif // [macOS] NSArray *_acceptDragAndDropTypes; #if TARGET_OS_OSX // [macOS + BOOL _enableFocusRing; NSArray *_readablePasteboardTypes; #endif // macOS] } @@ -64,6 +65,7 @@ - (instancetype)initWithFrame:(CGRect)frame // Fix blurry text on non-retina displays. self.canDrawSubviewsIntoLayer = YES; self.allowsUndo = YES; + _enableFocusRing = YES; #endif // macOS] _textInputDelegateAdapter = [[RCTBackedTextViewDelegateAdapter alloc] initWithTextView:self]; @@ -258,6 +260,21 @@ - (BOOL)resignFirstResponder return success; } + +- (NSFocusRingType)focusRingType +{ + return _enableFocusRing ? NSFocusRingTypeDefault : NSFocusRingTypeNone; +} + +- (void)setEnableFocusRing:(BOOL)enableFocusRing +{ + if (_enableFocusRing != enableFocusRing) { + _enableFocusRing = enableFocusRing; + } + + [super setFocusRingType:self.focusRingType]; + [self setKeyboardFocusRingNeedsDisplayInRect:self.bounds]; +} #endif // macOS] - (void)setDefaultTextAttributes:(NSDictionary *)defaultTextAttributes diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index 01114a943c70..d716f199955e 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -49,6 +49,9 @@ - (instancetype)initWithFrame:(CGRect)frame _forwardingTextView.textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX); _forwardingTextView.textContainer.widthTracksTextView = YES; _forwardingTextView.textInputDelegate = self; + if ([_forwardingTextView respondsToSelector:@selector(setEnableFocusRing:)]) { + [_forwardingTextView setEnableFocusRing:YES]; + } _scrollView.documentView = _forwardingTextView; _scrollView.contentView.postsBoundsChangedNotifications = YES; @@ -197,12 +200,19 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets - (BOOL)enableFocusRing { + if ([_forwardingTextView respondsToSelector:@selector(enableFocusRing)]) { + return [_forwardingTextView enableFocusRing]; + } + return _scrollView.enableFocusRing; } - (void)setEnableFocusRing:(BOOL)enableFocusRing { _scrollView.enableFocusRing = enableFocusRing; + if ([_forwardingTextView respondsToSelector:@selector(setEnableFocusRing:)]) { + [_forwardingTextView setEnableFocusRing:enableFocusRing]; + } } @end diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index ce4d634bc8e1..53c7bae09305 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -963,7 +963,10 @@ - (void)focus [_backedTextInputView becomeFirstResponder]; #else // [macOS NSWindow *window = [_backedTextInputView window]; - [window makeFirstResponder:_backedTextInputView]; + NSResponder *responder = [_backedTextInputView respondsToSelector:@selector(responder)] + ? _backedTextInputView.responder + : (NSResponder *)_backedTextInputView; + [window makeFirstResponder:responder]; #endif // macOS] const auto &props = static_cast(*_props); @@ -993,8 +996,13 @@ - (void)blur if ([_backedTextInputView isKindOfClass:[NSTextField class]] && [(NSTextField *)_backedTextInputView currentEditor] != nil) { [window makeFirstResponder:nil]; - } else if ([window firstResponder] == _backedTextInputView.responder) { - [window makeFirstResponder:nil]; + } else { + NSResponder *responder = [_backedTextInputView respondsToSelector:@selector(responder)] + ? _backedTextInputView.responder + : (NSResponder *)_backedTextInputView; + if ([window firstResponder] == responder) { + [window makeFirstResponder:nil]; + } } #endif // macOS] } diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 566d598cf95d..b3fe74cbc3cb 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -58,16 +58,23 @@ void RCTCopyBackedTextInput( toTextInput.clearButtonMode = fromTextInput.clearButtonMode; #endif // [macOS] toTextInput.scrollEnabled = fromTextInput.scrollEnabled; + toTextInput.disableKeyboardShortcuts = fromTextInput.disableKeyboardShortcuts; + toTextInput.acceptDragAndDropTypes = fromTextInput.acceptDragAndDropTypes; #if !TARGET_OS_OSX // [macOS] toTextInput.secureTextEntry = fromTextInput.secureTextEntry; toTextInput.keyboardType = fromTextInput.keyboardType; toTextInput.textContentType = fromTextInput.textContentType; toTextInput.smartInsertDeleteType = fromTextInput.smartInsertDeleteType; toTextInput.passwordRules = fromTextInput.passwordRules; - toTextInput.disableKeyboardShortcuts = fromTextInput.disableKeyboardShortcuts; - toTextInput.acceptDragAndDropTypes = fromTextInput.acceptDragAndDropTypes; [toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO]; +#else // [macOS] + toTextInput.enableFocusRing = fromTextInput.enableFocusRing; + toTextInput.pointScaleFactor = fromTextInput.pointScaleFactor; + toTextInput.automaticSpellingCorrectionEnabled = fromTextInput.automaticSpellingCorrectionEnabled; + toTextInput.grammarCheckingEnabled = fromTextInput.grammarCheckingEnabled; + toTextInput.continuousSpellCheckingEnabled = fromTextInput.continuousSpellCheckingEnabled; + [toTextInput setSelectedTextRange:[fromTextInput selectedTextRange] notifyDelegate:NO]; #endif // [macOS] }