From 6daef14637bea3c23c632228ee142f7ec0df9e4d Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 7 Dec 2023 23:58:16 +0100 Subject: [PATCH 01/24] [fabric] Add wrapper class for TextView with scroll callback support Summary: The multiline text input view on macOS needs its own view hierarchy, wrapping the RCTUITextView in a scroll view to support all the features offered by the React TextInput component. This diff adds a wrapper class for RCTUITextView that provides the appropriate view hierarchy while still supporting the text input protocols required for text input. The wrapper forwards all unimplemented methods to the RCTUITextView so that it can be used as a direct substitute for the RCTUITextView. This allows us to reduce the custom changes need for macOS in RCTTextInputComponentView while re-using all the logic in RCTUITextView. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51962394 Tasks: T167538822, T157889591 Tags: uikit-diff --- .../TextInput/Multiline/RCTWrappedTextView.h | 28 +++ .../TextInput/Multiline/RCTWrappedTextView.m | 194 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h create mode 100644 packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h new file mode 100644 index 00000000000000..c487c96372da17 --- /dev/null +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#if TARGET_OS_OSX // [macOS + +#import + +#import "RCTTextUIKit.h" + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTWrappedTextView : RCTPlatformView + +@property (nonatomic, weak) id textInputDelegate; +@property (assign) BOOL hideVerticalScrollIndicator; + +@end + +NS_ASSUME_NONNULL_END + +#endif // macOS] diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m new file mode 100644 index 00000000000000..6481dadf69de49 --- /dev/null +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -0,0 +1,194 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#if TARGET_OS_OSX // [macOS + +#import + +#import +#import + +@implementation RCTWrappedTextView { + RCTUITextView *_forwardingTextView; + RCTUIScrollView *_scrollView; + RCTClipView *_clipView; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + self.hideVerticalScrollIndicator = NO; + + _scrollView = [[RCTUIScrollView alloc] initWithFrame:self.bounds]; + _scrollView.backgroundColor = [RCTUIColor clearColor]; + _scrollView.drawsBackground = NO; + _scrollView.borderType = NSNoBorder; + _scrollView.hasHorizontalRuler = NO; + _scrollView.hasVerticalRuler = NO; + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [_scrollView setHasVerticalScroller:YES]; + [_scrollView setHasHorizontalScroller:NO]; + + _clipView = [[RCTClipView alloc] initWithFrame:_scrollView.bounds]; + [_scrollView setContentView:_clipView]; + + _forwardingTextView = [[RCTUITextView alloc] initWithFrame:_scrollView.bounds]; + _forwardingTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _forwardingTextView.delegate = self; + + _forwardingTextView.verticallyResizable = YES; + _forwardingTextView.horizontallyResizable = YES; + _forwardingTextView.textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX); + _forwardingTextView.textContainer.widthTracksTextView = YES; + _forwardingTextView.textInputDelegate = self; + + _scrollView.documentView = _forwardingTextView; + _scrollView.contentView.postsBoundsChangedNotifications = YES; + + // Enable the focus ring by default + _scrollView.enableFocusRing = YES; + [self addSubview:_scrollView]; + + // a register for those notifications on the content view. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(boundsDidChange:) + name:NSViewBoundsDidChangeNotification + object:_scrollView.contentView]; + } + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (BOOL)isFlipped +{ + return YES; +} + +#pragma mark - +#pragma mark Method forwarding to text view + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + [invocation invokeWithTarget:_forwardingTextView]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + if ([_forwardingTextView respondsToSelector:selector]) { + return [_forwardingTextView methodSignatureForSelector:selector]; + } + + return [super methodSignatureForSelector:selector]; +} + +- (void)boundsDidChange:(NSNotification *)notification +{ +} + +#pragma mark - +#pragma mark First Responder forwarding + +- (NSResponder *)responder +{ + return _forwardingTextView; +} + +- (BOOL)acceptsFirstResponder +{ + return _forwardingTextView.acceptsFirstResponder; +} + +- (BOOL)becomeFirstResponder +{ + return [_forwardingTextView becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder +{ + return [_forwardingTextView resignFirstResponder]; +} + +#pragma mark - +#pragma mark Text Input delegate forwarding + +- (id)textInputDelegate +{ + return _forwardingTextView.textInputDelegate; +} + +- (void)setTextInputDelegate:(id)textInputDelegate +{ + _forwardingTextView.textInputDelegate = textInputDelegate; +} + +#pragma mark - +#pragma mark Scrolling control + +- (BOOL)scrollEnabled +{ + return _scrollView.isScrollEnabled; +} + +- (void)setScrollEnabled:(BOOL)scrollEnabled +{ + if (scrollEnabled) { + _scrollView.scrollEnabled = YES; + [_clipView setConstrainScrolling:NO]; + } else { + _scrollView.scrollEnabled = NO; + [_clipView setConstrainScrolling:YES]; + } +} + +- (BOOL)shouldShowVerticalScrollbar +{ + // Hide vertical scrollbar if explicity set to NO + if (self.hideVerticalScrollIndicator) { + return NO; + } + + // Hide vertical scrollbar if attributed text overflows view + CGSize textViewSize = [_forwardingTextView intrinsicContentSize]; + NSClipView *clipView = (NSClipView *)_scrollView.contentView; + if (textViewSize.height > clipView.bounds.size.height) { + return YES; + }; + + return NO; +} + +- (void)textInputDidChange +{ + [_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]]; +} + +- (void)setAttributedText:(NSAttributedString *)attributedText +{ + [_forwardingTextView setAttributedText:attributedText]; + [_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]]; +} + +#pragma mark - +#pragma mark Text Container Inset override for NSTextView + +// This method is there to match the textContainerInset property on RCTUITextField +- (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets +{ + // RCTUITextView has logic in setTextContainerInset[s] to convert th UIEdgeInsets to a valid NSSize struct + _forwardingTextView.textContainerInsets = textContainerInsets; +} + +@end + +#endif // macOS] From 0994e37d1a968218dfc8758b1f0b8f06b724952a Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Oct 2025 11:25:33 -0700 Subject: [PATCH 02/24] slight modifications --- .../Text/TextInput/Multiline/RCTWrappedTextView.h | 8 +++++--- .../Text/TextInput/Multiline/RCTWrappedTextView.m | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h index c487c96372da17..e63d0b3cd8ee1e 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h @@ -1,11 +1,13 @@ /* - * Copyright (c) Meta Platforms, Inc. and affiliates. + * Copyright (c) Microsoft Corporation. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -#if TARGET_OS_OSX // [macOS +// [macOS] + +#if TARGET_OS_OSX #import @@ -25,4 +27,4 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END -#endif // macOS] +#endif // TARGET_OS_OSX diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index 6481dadf69de49..bf04fe8b04a5d9 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -1,11 +1,13 @@ /* - * Copyright (c) Meta Platforms, Inc. and affiliates. + * Copyright (c) Microsoft Corporation. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -#if TARGET_OS_OSX // [macOS + // [macOS] + +#if TARGET_OS_OSX #import @@ -185,10 +187,10 @@ - (void)setAttributedText:(NSAttributedString *)attributedText // This method is there to match the textContainerInset property on RCTUITextField - (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets { - // RCTUITextView has logic in setTextContainerInset[s] to convert th UIEdgeInsets to a valid NSSize struct + // RCTUITextView has logic in setTextContainerInset[s] to convert the UIEdgeInsets to a valid NSSize struct _forwardingTextView.textContainerInsets = textContainerInsets; } @end -#endif // macOS] +#endif // TARGET_OS_OSX From c7ecd3c183faeb2f75556a48abc11c85eb616dd8 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Dec 2023 00:02:32 +0100 Subject: [PATCH 03/24] [fabric] Add responder property to backing text input view protocol Summary: Add a `responder` property to support assigning the first responder to the actual textfield/textview if the view is wrapped or not. The wrapped text view already implements this property. This diff brings the same functionality to the text field and declares it on the common protocol. Test Plan: Tested later in this stack. Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51962395 Tasks: T167538822, T157889591 --- .../Text/TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../Libraries/Text/TextInput/Singleline/RCTUITextField.mm | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 8d3431ac598573..7775ca596bf78c 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -38,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readonly) BOOL textWasPasted; #else // [macOS @property (nonatomic, assign) BOOL textWasPasted; +@property (nonatomic, readonly) NSResponder *responder; #endif // macOS] @property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, assign) UIEdgeInsets textContainerInset; diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index ea73d98628da3f..f1d87a07ea71b3 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -217,6 +217,11 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset #if TARGET_OS_OSX // [macOS +- (NSResponder *)responder +{ + return self; +} + + (Class)cellClass { return RCTUITextFieldCell.class; From 11ab6cd57065705948791b98f291cc318bb4aea6 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Dec 2023 00:12:33 +0100 Subject: [PATCH 04/24] [fabric] Use wrapped text view for multiline TextInput Summary: This diff updates the core TextInput RN component to use the wrapped text view for multiline TextInput. Switching to `RCTWrappedTextView` enables correct `borderWidth` and `contentInsets` support for multi line text inputs while maintaining the same functionality for single line text input. Scrolling text views are also supported correctly, with vertical height dependent scrollers. Test Plan: - Build Zeratul with Fabric enabled. - Type in the search field to test the layout of the text contents - Type in the composer to test multi line support and the layout of the text contents | Before | After | |--| | https://pxl.cl/3Xrrx | https://pxl.cl/3Xrr9 | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51962396 Tasks: T167538822, T157889591 --- .../TextInput/RCTTextInputComponentView.mm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 440a574268d1eb..3a4fa500300a56 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -16,6 +16,9 @@ #import #import #import +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] #import "RCTConversions.h" #import "RCTTextInputNativeCommands.h" @@ -86,7 +89,11 @@ - (instancetype)initWithFrame:(CGRect)frame const auto &defaultProps = TextInputShadowNode::defaultSharedProps(); _props = defaultProps; +#if !TARGET_OS_OSX // [macOS] _backedTextInputView = defaultProps->multiline ? [RCTUITextView new] : [RCTUITextField new]; +#else // [macOS + _backedTextInputView = defaultProps->multiline ? [[RCTWrappedTextView alloc] initWithFrame:self.bounds] : [RCTUITextField new]; +#endif // macOS] _backedTextInputView.textInputDelegate = self; _ignoreNextTextInputCall = NO; _comingFromJS = NO; @@ -672,7 +679,7 @@ - (void)blur [_backedTextInputView resignFirstResponder]; #else // [macOS NSWindow *window = [_backedTextInputView window]; - if ([window firstResponder] == _backedTextInputView) { + if ([window firstResponder] == _backedTextInputView.responder) { [window makeFirstResponder:nil]; } #endif // macOS] @@ -967,7 +974,7 @@ - (void)_setMultiline:(BOOL)multiline #if !TARGET_OS_OSX // [macOS] RCTUIView *backedTextInputView = multiline ? [RCTUITextView new] : [RCTUITextField new]; #else // [macOS - RCTUITextView *backedTextInputView = [RCTUITextView new]; + RCTPlatformView *backedTextInputView = multiline ? [RCTWrappedTextView new] : [RCTUITextField new]; #endif // macOS] backedTextInputView.frame = _backedTextInputView.frame; RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView); From dadf2de90a806944a997a1f13165662658458e62 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:42:29 +0100 Subject: [PATCH 05/24] [fabric] Support showing/hiding the focus ring for TextInput Summary: This diff propagates the View `enableFocusRing` property to the backing textfield/textview to correctly set up the NSTextField/NSScrollView with the latest provided property value for single and multiline TextInput components. Test Plan: - Run Zeratul with Fabric enabled - Focus the composer in a message thread - The blue focus ring should not show up around the composer | Before | After | |--| | https://pxl.cl/3XHP8 | {F1169320851} | Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52005858 Tasks: T167538822 --- .../Text/TextInput/Multiline/RCTWrappedTextView.m | 13 +++++++++++++ .../Text/TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/RCTTextInputComponentView.mm | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index bf04fe8b04a5d9..d45069a87f6c3e 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -191,6 +191,19 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets _forwardingTextView.textContainerInsets = textContainerInsets; } +#pragma mark - +#pragma mark Focus ring + +- (BOOL)enableFocusRing +{ + return _scrollView.enableFocusRing; +} + +- (void)setEnableFocusRing:(BOOL)enableFocusRing +{ + _scrollView.enableFocusRing = enableFocusRing; +} + @end #endif // TARGET_OS_OSX diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 7775ca596bf78c..a57420235dda61 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN #else // [macOS @property (nonatomic, assign) BOOL textWasPasted; @property (nonatomic, readonly) NSResponder *responder; +@property (nonatomic, assign) BOOL enableFocusRing; #endif // macOS] @property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, assign) UIEdgeInsets textContainerInset; 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 3a4fa500300a56..94991da7059af2 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -557,6 +557,13 @@ - (void)textInputDidChangeSelection } #if TARGET_OS_OSX // [macOS +- (void)setEnableFocusRing:(BOOL)enableFocusRing { + [super setEnableFocusRing:enableFocusRing]; + if ([_backedTextInputView respondsToSelector:@selector(setEnableFocusRing:)]) { + [_backedTextInputView setEnableFocusRing:enableFocusRing]; + } +} + - (void)automaticSpellingCorrectionDidChange:(BOOL)enabled { if (_eventEmitter) { std::static_pointer_cast(_eventEmitter)->onAutoCorrectChange({.autoCorrectEnabled = static_cast(enabled)}); From a03e7596e3e686949eadc2d5cd36ec63985ca5d7 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:46:56 +0100 Subject: [PATCH 06/24] [fabric] Implement escape/cancel key press callback for TextInput Summary: Pressing the escape key down in a TextInput on macOS will be captured on the native side as a cancel event. This diff adds support for sending the "Escape" key as a key pressed event and wires the delegate method of the backing text input to call the key press event handler with the correct payload. Test Plan: - Run Zeratul with Fabric enabled - Add a key press handler on the textfield logging the received data (e.g. on MDSTextInput) - Focus the textfield - Press the escape key - The key press handler should be called and print that the "Escape" key was pressed. https://pxl.cl/3XHR2 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52005857 Tasks: T167538822 --- .../TextInput/RCTTextInputComponentView.mm | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 94991da7059af2..a9534c99f9d652 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -586,7 +586,24 @@ - (void)grammarCheckingDidChange:(BOOL)enabled - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event {} -- (void)textInputDidCancel {} +- (void)textInputDidCancel +{ + if (_eventEmitter) { + KeyPressMetrics keyPressMetrics; + keyPressMetrics.text = RCTStringFromNSString(@"\x1B"); // Escape key + keyPressMetrics.eventCount = _mostRecentEventCount; + + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + auto const &props = *std::static_pointer_cast(_props); + if (props.onKeyPressSync) { + textInputEventEmitter.onKeyPressSync(keyPressMetrics); + } else { + textInputEventEmitter.onKeyPress(keyPressMetrics); + } + } + + [self textInputDidEndEditing]; +} - (NSDragOperation)textInputDraggingEntered:(nonnull id)draggingInfo { if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) { From e5f5425ae48349acf03f768c3bd152fdd9e11945 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:52:23 +0100 Subject: [PATCH 07/24] [fabric] Submit scroll view metrics when scrolling multiline TextInput Summary: This diff implements the scrollViewDidScroll delegate method call on the mutli line text input (`RCTWrappedTextView`). The text metrics are updated with the right values read directly from the emitting scroll view so that the TextInput would submit the right payload for scrolling multi line TextInput components at all times. Test Plan: - Run Zeratul with Fabric enabled - Log the onScroll callback data for TextInput used by the composer (e.g. MDSTextInput) - Type a message spanning over multiple lines in the composer until a scroll bar is visible - Scroll the composer contents - The logging shows the text metrics provided as payload for the onScroll handler. https://pxl.cl/3XHRl Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52005859 Tasks: T167538822 --- .../TextInput/Multiline/RCTWrappedTextView.m | 14 +++++++++++++ .../RCTBackedTextInputDelegateAdapter.mm | 6 +++--- .../TextInput/RCTTextInputComponentView.mm | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index d45069a87f6c3e..59f72e53bbe945 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -58,10 +58,17 @@ - (instancetype)initWithFrame:(CGRect)frame [self addSubview:_scrollView]; // a register for those notifications on the content view. + #if !TARGET_OS_OSX // [macOS] [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewBoundsDidChangeNotification object:_scrollView.contentView]; + #else // [macOS + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scrollViewDidScroll:) + name:NSViewBoundsDidChangeNotification + object:_scrollView.contentView]; + #endif // macOS] } return self; @@ -137,6 +144,13 @@ - (void)setTextInputDelegate:(id)textInputDelegate #pragma mark - #pragma mark Scrolling control +#if TARGET_OS_OSX // [macOS +- (void)scrollViewDidScroll:(NSNotification *)notification +{ + [self.textInputDelegate scrollViewDidScroll:_scrollView]; +} +#endif // macOS] + - (BOOL)scrollEnabled { return _scrollView.isScrollEnabled; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm index 7fe50d5b24d67a..6aaa1905c0c13d 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm @@ -398,17 +398,17 @@ - (void)textViewDidChangeSelection:(__unused UITextView *)textView [self textViewProbablyDidChangeSelection]; } +#endif // [macOS] + #pragma mark - UIScrollViewDelegate -- (void)scrollViewDidScroll:(UIScrollView *)scrollView +- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] { if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { [_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView]; } } -#endif // [macOS] - #if TARGET_OS_OSX // [macOS #pragma mark - NSTextViewDelegate 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 a9534c99f9d652..858130430f4d34 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -662,7 +662,28 @@ - (BOOL)textInputShouldHandlePaste:(nonnull id)s - (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] { if (_eventEmitter) { +#if !TARGET_OS_OSX // [macOS] static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); +#else // [macOS + TextInputMetrics metrics = [self _textInputMetrics]; // [macOS] + + CGPoint contentOffset = scrollView.contentOffset; + metrics.contentOffset = {contentOffset.x, contentOffset.y}; + + UIEdgeInsets contentInset = scrollView.contentInset; + metrics.contentInset = {contentInset.left, contentInset.top, contentInset.right, contentInset.bottom}; + + CGSize contentSize = scrollView.contentSize; + metrics.contentSize = {contentSize.width, contentSize.height}; + + CGSize layoutMeasurement = scrollView.bounds.size; + metrics.layoutMeasurement = {layoutMeasurement.width, layoutMeasurement.height}; + + CGFloat zoomScale = scrollView.zoomScale ?: 1; + metrics.zoomScale = zoomScale; + + static_cast(*_eventEmitter).onScroll(metrics); +#endif // macOS] } } From 5df9acdf47211405a1bb1337f88a83959c42f900 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Oct 2025 15:34:11 -0700 Subject: [PATCH 08/24] remove odd ifdef --- .../Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index 59f72e53bbe945..020931ba0a641d 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -58,17 +58,14 @@ - (instancetype)initWithFrame:(CGRect)frame [self addSubview:_scrollView]; // a register for those notifications on the content view. - #if !TARGET_OS_OSX // [macOS] [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewBoundsDidChangeNotification object:_scrollView.contentView]; - #else // [macOS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollViewDidScroll:) name:NSViewBoundsDidChangeNotification object:_scrollView.contentView]; - #endif // macOS] } return self; From 20c8212108441034c00955eedb1edf2ab8e2a34c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 15 Jan 2024 20:23:58 +0100 Subject: [PATCH 09/24] [fabric] Fix random TextInput cursor position changes while typing Summary: When text completions or special character symbols are displayed ahead of time, the content is present in the native text view attributed string. If a state update is applied, the cursor position will not be read as being at the end of the string while typing, which will shift the cursor position after the update. This diff disables native text view content updates while temporary context is displayed to fix the wrong cursor position updates. Test Plan: Run Zeratul with Fabric and type text in the message composer until macOS renders a text completion/suggestion. Keep typing and see if the cursor stays at the end of the string. | Before | After | |--| | https://pxl.cl/49PW4 | https://pxl.cl/49PWh | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52787094 Tasks: T174286406 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 7 +++++++ 1 file changed, 7 insertions(+) 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 858130430f4d34..3a8870c9e19c21 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -938,6 +938,13 @@ - (void)_restoreTextSelection - (void)_setAttributedString:(NSAttributedString *)attributedString { +#if TARGET_OS_OSX // [macOS + // When the text view displays temporary content (e.g. completions, accents), do not update the attributed string. + if (_backedTextInputView.hasMarkedText) { + return; + } +#endif // macOS] + if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) { return; } From 113fe8a1db7c67d3bcf846404d28aa2b075b6dea Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:10:00 +0100 Subject: [PATCH 10/24] [fabric] Add submitKeyEvents property to TextInput Summary: This diff adds the macOS-only property to the TextInput component called `submitKeyEvents` which allows the user to provide key combinations that will trigger a submit action. This is being used to implement message sending on hitting the return key. Test Plan: Tested later in this stack Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Subscribers: taskcreeper Differential Revision: https://phabricator.intern.facebook.com/D52860413 --- .../components/iostextinput/primitives.h | 17 +++++++ .../iostextinput/propsConversions.h | 45 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h index a6d703106d8967..f02596aebba1a2 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h @@ -89,6 +89,16 @@ enum class PastedTypesType { Image, String, }; + +class SubmitKeyEvent final { + public: + std::string key{}; + bool altKey{false}; + bool shiftKey{false}; + bool ctrlKey{false}; + bool metaKey{false}; + bool functionKey{false}; +}; #endif // macOS] /* @@ -242,6 +252,13 @@ class TextInputTraits final { * Default value: `empty list` */ std::vector pastedTypes{}; + + /* + * List of key combinations that should submit. + * macOS + * Default value: `empty` applies as 'Enter' key. + */ + std::vector submitKeyEvents{}; #endif // macOS] }; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h index 0cb3bfc1ac3c10..77e2111b4a02e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h @@ -193,5 +193,50 @@ inline void fromRawValue( } else { LOG(ERROR) << "Unsupported Selection type"; } + +#if TARGET_OS_OSX // [macOS +static inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + SubmitKeyEvent &result) { + auto map = (std::unordered_map)value; + + auto tmp_key = map.find("key"); + if (tmp_key != map.end()) { + fromRawValue(context, tmp_key->second, result.key); + } + auto tmp_altKey = map.find("altKey"); + if (tmp_altKey != map.end()) { + fromRawValue(context, tmp_altKey->second, result.altKey); + } + auto tmp_shiftKey = map.find("shiftKey"); + if (tmp_shiftKey != map.end()) { + fromRawValue(context, tmp_shiftKey->second, result.shiftKey); + } + auto tmp_ctrlKey = map.find("ctrlKey"); + if (tmp_ctrlKey != map.end()) { + fromRawValue(context, tmp_ctrlKey->second, result.ctrlKey); + } + auto tmp_metaKey = map.find("metaKey"); + if (tmp_metaKey != map.end()) { + fromRawValue(context, tmp_metaKey->second, result.metaKey); + } + auto tmp_functionKey = map.find("functionKey"); + if (tmp_functionKey != map.end()) { + fromRawValue(context, tmp_functionKey->second, result.functionKey); + } +} + +static inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + SubmitKeyEvent newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } } +#endif // macOS] } // namespace facebook::react From dcfe45358b21bc0d9a7bdbaed5ae0de8f2070b0c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:11:42 +0100 Subject: [PATCH 11/24] [fabric] Implement TextInput key down event checking for submit Summary: This diff implements the key down event processing to check if any of the provided submit key combinations were entered in the TextInput. If a match is found, the submit callback is triggered. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52860412 --- .../TextInput/RCTTextInputComponentView.mm | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) 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 3a8870c9e19c21..e2ee722af759f0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -18,6 +18,7 @@ #import #if TARGET_OS_OSX // [macOS #import +#import #endif // macOS] #import "RCTConversions.h" @@ -584,7 +585,44 @@ - (void)grammarCheckingDidChange:(BOOL)enabled } } -- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event {} +- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event +{ + BOOL shouldSubmit = NO; + NSDictionary *keyEvent = [RCTViewKeyboardEvent bodyFromEvent:event]; + auto const &props = *std::static_pointer_cast(_props); + if (props.traits.submitKeyEvents.empty()) { + shouldSubmit = [keyEvent[@"key"] isEqualToString:@"Enter"] + && ![keyEvent[@"altKey"] boolValue] + && ![keyEvent[@"shiftKey"] boolValue] + && ![keyEvent[@"ctrlKey"] boolValue] + && ![keyEvent[@"metaKey"] boolValue] + && ![keyEvent[@"functionKey"] boolValue]; // Default clearTextOnSubmit key + } else { + NSString *keyValue = keyEvent[@"key"]; + NSUInteger keyValueLength = [keyValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + std::string key = std::string([keyValue UTF8String], keyValueLength); + for (auto const &submitKeyEvent : props.traits.submitKeyEvents) { + if ( + submitKeyEvent.key == key && + submitKeyEvent.altKey == [keyEvent[@"altKey"] boolValue] && + submitKeyEvent.shiftKey == [keyEvent[@"shiftKey"] boolValue] && + submitKeyEvent.ctrlKey == [keyEvent[@"ctrlKey"] boolValue] && + submitKeyEvent.metaKey == [keyEvent[@"metaKey"] boolValue] && + submitKeyEvent.functionKey == [keyEvent[@"functionKey"] boolValue] + ) { + shouldSubmit = YES; + break; + } + } + } + + if (shouldSubmit) { + if (_eventEmitter) { + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + textInputEventEmitter.onSubmitEditing([self _textInputMetrics]); + } + } +} - (void)textInputDidCancel { From 7311f83fe2840f3d3461439b427513adb96a80cd Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:24:57 +0100 Subject: [PATCH 12/24] [fabric] Add support for clearing the TextInput on submit Summary: This diff adds the macOS-only `clearTextOnSubmit` property for the TextInput component. When set to `true`, the TextInput will clear its content after a submit was triggered through the configured `submitKeyEvents` key combinations. Test Plan: - Run Zeratul with Fabric enabled and enter a message in the composer. - Submit the message by pressing the `Return` key. - Check that `Shift + Return` still works without submitting. https://pxl.cl/4bw3D Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52860414 --- .../TextInput/RCTTextInputComponentView.mm | 5 +++++ .../renderer/components/iostextinput/primitives.h | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) 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 e2ee722af759f0..7967638135714d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -621,6 +621,11 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); textInputEventEmitter.onSubmitEditing([self _textInputMetrics]); } + + if (props.traits.clearTextOnSubmit) { + _backedTextInputView.attributedText = nil; + [self textInputDidChange]; + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h index f02596aebba1a2..92a13cf4c157cc 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/primitives.h @@ -241,7 +241,7 @@ class TextInputTraits final { #if TARGET_OS_OSX // [macOS /* * Can be empty (`null` in JavaScript) which means `default`. - * maOS + * macOS * Default value: `empty` (`null`). */ std::optional grammarCheck{}; @@ -255,10 +255,17 @@ class TextInputTraits final { /* * List of key combinations that should submit. - * macOS - * Default value: `empty` applies as 'Enter' key. + * macOS-only + * Default value: `empty list` applies as 'Enter' key. */ std::vector submitKeyEvents{}; + + /* + * When set to `true`, the text will be cleared after the submit. + * macOS-only + * Default value: `false` + */ + bool clearTextOnSubmit{false}; #endif // macOS] }; From bd7408b57616111cef6e272fe448d8b13f1bfa17 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 19 Jan 2024 20:40:51 +0100 Subject: [PATCH 13/24] [fabric] Add support for the secure text entry to TextInput Summary: This diff adds support for the secure text entry mode of the single line TextInput component. The backing text field is switched for an extension of the NSSecureTextField on macOS. This was implemented on the single line text input implementation in Paper. This logic is now combined with the multi line text input on the RCTTextInputComponentView on Fabric. The secure text field uses a macro define to re-include the text field source files with minor changes, this to avoid code duplication. To support the inclusion magic happening here, the `import` statement of the text field header had to be converted to an `include` so that the macro-toggling-magic of the secure text field would work. Test Plan: - Run Zeratul with Fabric enabled and use the auth screen of Messenger to test the secure text entry with the password field. https://pxl.cl/4c2CD Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52924481 --- .../TextInput/RCTTextInputComponentView.mm | 34 ++++++++++++++++++- .../TextInput/RCTTextInputUtils.mm | 4 +++ 2 files changed, 37 insertions(+), 1 deletion(-) 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 7967638135714d..1269fcad4aa99b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -13,7 +13,14 @@ #import #import + +#if !TARGET_OS_OSX // [macOS] #import +#else // [macOS +#include +#include +#endif // macOS] + #import #import #if TARGET_OS_OSX // [macOS @@ -280,11 +287,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled; } -#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) { +#if !TARGET_OS_OSX // [macOS] _backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry; +#else // [macOS + [self _setSecureTextEntry:newTextInputProps.traits.secureTextEntry]; +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) { _backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType); } @@ -1096,6 +1107,27 @@ - (void)_setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus } #endif // macOS] +#if TARGET_OS_OSX // [macOS +- (void)_setSecureTextEntry:(BOOL)secureTextEntry +{ + [_backedTextInputView removeFromSuperview]; + RCTPlatformView *backedTextInputView = secureTextEntry ? [RCTUISecureTextField new] : [RCTUITextField new]; + backedTextInputView.frame = _backedTextInputView.frame; + RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView); + + // Copy the text field specific properties if we came from a single line input before the switch + if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) { + RCTUITextField *previousTextField = (RCTUITextField *)_backedTextInputView; + RCTUITextField *newTextField = (RCTUITextField *)backedTextInputView; + newTextField.textAlignment = previousTextField.textAlignment; + newTextField.text = previousTextField.text; + } + + _backedTextInputView = backedTextInputView; + [self addSubview:_backedTextInputView]; +} +#endif // macOS] + - (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText { // When the dictation is running we can't update the attributed text on the backed up text view 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 18a62f52dbf637..6c76ea3b7ce775 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -27,6 +27,10 @@ void RCTCopyBackedTextInput( toTextInput.placeholder = fromTextInput.placeholder; toTextInput.placeholderColor = fromTextInput.placeholderColor; toTextInput.textContainerInset = fromTextInput.textContainerInset; + +#if TARGET_OS_OSX // [macOS + toTextInput.autoresizingMask = fromTextInput.autoresizingMask; +#endif // macOS] #if TARGET_OS_IOS // [macOS] [visionOS] toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView; #endif // [macOS] [visionOS] From 327568559fb0aaf939085190816b48e0d6f09a8b Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 19 Jan 2024 20:43:51 +0100 Subject: [PATCH 14/24] [fabric] Copy accessibility attributes when switching TextInput backing view Summary: This diff adds the copy of the a11y attributes when switching the TextInput to the secure text entry which triggers the switch to a NSSecureTextField and runs the copy of the NSTextField attributes to the new backing text field. Test Plan: - Run Zeratul with Fabric enabled and inspect the Messenger password field on the auth screen using the Accessability Inspector to check that the attributes are passed down to the native text field. {F1332886925} Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52924480 --- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm | 5 +++++ 1 file changed, 5 insertions(+) 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 6c76ea3b7ce775..9aadf718703609 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -29,6 +29,11 @@ void RCTCopyBackedTextInput( toTextInput.textContainerInset = fromTextInput.textContainerInset; #if TARGET_OS_OSX // [macOS + toTextInput.accessibilityElement = fromTextInput.accessibilityElement; + toTextInput.accessibilityHelp = fromTextInput.accessibilityHelp; + toTextInput.accessibilityIdentifier = fromTextInput.accessibilityIdentifier; + toTextInput.accessibilityLabel = fromTextInput.accessibilityLabel; + toTextInput.accessibilityRole = fromTextInput.accessibilityRole; toTextInput.autoresizingMask = fromTextInput.autoresizingMask; #endif // macOS] #if TARGET_OS_IOS // [macOS] [visionOS] From 5f3faf6c8d8d22518a164e7eda2c76660fa48965 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 13 Mar 2024 16:47:06 -0700 Subject: [PATCH 15/24] [fabric] TextInput should get focus with `autoFocus` prop Summary: **Context** - TextInput focus prop was not wired up for Fabric **Change** - Focus the text input if `autofocus={true}` Test Plan: Tested in top of stack Reviewers: lefever, #rn-desktop Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D54880714 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 1269fcad4aa99b..5b25cecc8f50f8 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -142,7 +142,12 @@ - (void)didMoveToWindow if (props.autoFocus) { #if !TARGET_OS_OSX // [macOS] [_backedTextInputView becomeFirstResponder]; -#endif // [macOS] +#else // [macOS + NSWindow *window = [_backedTextInputView window]; + if (window) { + [window makeFirstResponder:_backedTextInputView.responder]; + } +#endif // macOS] [self scrollCursorIntoView]; } _didMoveToWindow = YES; From c1df4ad98c1d191f6f38a05b5b81a29dcd3a684c Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 1 May 2024 13:44:46 -0700 Subject: [PATCH 16/24] [fabric] Fix warning & formatting --- .../Libraries/Text/TextInput/Singleline/RCTUITextField.h | 4 ++-- .../RCTLegacyViewManagerInteropComponentView.mm | 4 ---- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.h | 2 -- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm | 2 -- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h index 14735c2cd509eb..ff30d49d7f7f28 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN #if !TARGET_OS_OSX // [macOS] @property (nonatomic, assign, getter=isEditable) BOOL editable; #else // [macOS -@property (assign, getter=isEditable) BOOL editable; +@property (atomic, assign, getter=isEditable) BOOL editable; #endif // macOS] @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; @@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS @property (nonatomic, copy, nullable) NSString *text; @property (nonatomic, copy, nullable) NSAttributedString *attributedText; -@property (nonatomic, copy) NSDictionary *defaultTextAttributes; +@property (nonatomic, strong, nullable) NSDictionary *defaultTextAttributes; @property (nullable, nonatomic, copy) NSDictionary *typingAttributes; @property (nonatomic, assign) NSTextAlignment textAlignment; @property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 150f2f3b69a203..4dff1405818ef6 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -16,10 +16,6 @@ #import #import "RCTLegacyViewManagerInteropCoordinatorAdapter.h" -#if TARGET_OS_OSX // [macOS -#import -#endif // macOS] - using namespace facebook::react; static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView"; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index 619bb3164408a7..5628dfa338d8e1 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -30,9 +30,7 @@ UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizati UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance( facebook::react::KeyboardAppearance keyboardAppearance); -#endif // [macOS] -#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck); UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode( 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 9aadf718703609..68e767250e8545 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -103,9 +103,7 @@ UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppea return UIKeyboardAppearanceDark; } } -#endif // [macOS] -#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck) { return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo) From 84f6f5078afed1aa76a9da20c2c5baf9ade8ae5a Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 10:40:24 -0700 Subject: [PATCH 17/24] build fixes --- .../TextInput/RCTTextInputComponentView.mm | 15 +++++---------- .../components/iostextinput/propsConversions.h | 15 ++++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) 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 5b25cecc8f50f8..d4236fbd8a6f98 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -648,17 +648,12 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event - (void)textInputDidCancel { if (_eventEmitter) { - KeyPressMetrics keyPressMetrics; - keyPressMetrics.text = RCTStringFromNSString(@"\x1B"); // Escape key - keyPressMetrics.eventCount = _mostRecentEventCount; auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); - auto const &props = *std::static_pointer_cast(_props); - if (props.onKeyPressSync) { - textInputEventEmitter.onKeyPressSync(keyPressMetrics); - } else { - textInputEventEmitter.onKeyPress(keyPressMetrics); - } + textInputEventEmitter.onKeyPress({ + .text = RCTStringFromNSString(@"\x1B"), // Escape key + .eventCount = static_cast(_mostRecentEventCount), + }); } [self textInputDidEndEditing]; @@ -724,7 +719,7 @@ - (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] #if !TARGET_OS_OSX // [macOS] static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); #else // [macOS - TextInputMetrics metrics = [self _textInputMetrics]; // [macOS] + auto metrics = [self _textInputMetrics]; CGPoint contentOffset = scrollView.contentOffset; metrics.contentOffset = {contentOffset.x, contentOffset.y}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h index 77e2111b4a02e0..b1c599f38e4e65 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h @@ -193,12 +193,13 @@ inline void fromRawValue( } else { LOG(ERROR) << "Unsupported Selection type"; } +} #if TARGET_OS_OSX // [macOS -static inline void fromRawValue( +inline void fromRawValue( const PropsParserContext &context, - const RawValue &value, - SubmitKeyEvent &result) { + const RawValue& value, + SubmitKeyEvent& result) { auto map = (std::unordered_map)value; auto tmp_key = map.find("key"); @@ -227,10 +228,10 @@ static inline void fromRawValue( } } -static inline void fromRawValue( - const PropsParserContext &context, - const RawValue &value, - std::vector &result) { +inline void fromRawValue( + const PropsParserContext& context, + const RawValue& value, + std::vector& result) { auto items = (std::vector)value; for (const auto &item : items) { SubmitKeyEvent newItem; From 0000b9c456126a78a902afbec55f0453964da925 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 10:42:05 -0700 Subject: [PATCH 18/24] PR feedback + std::any_of --- .../TextInput/RCTTextInputComponentView.mm | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) 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 d4236fbd8a6f98..da07f933ab9132 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -39,6 +39,11 @@ static const CGFloat kSingleLineKeyboardBottomOffset = 15.0; #endif // [macOS] +#if TARGET_OS_OSX // [macOS +static const NSString *kEscapeKeyCode = @"\x1B"; +#endif // macOS] + + using namespace facebook::react; @interface RCTTextInputComponentView () @@ -144,9 +149,7 @@ - (void)didMoveToWindow [_backedTextInputView becomeFirstResponder]; #else // [macOS NSWindow *window = [_backedTextInputView window]; - if (window) { - [window makeFirstResponder:_backedTextInputView.responder]; - } + [window makeFirstResponder:_backedTextInputView.responder]; #endif // macOS] [self scrollCursorIntoView]; } @@ -615,20 +618,23 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event && ![keyEvent[@"functionKey"] boolValue]; // Default clearTextOnSubmit key } else { NSString *keyValue = keyEvent[@"key"]; - NSUInteger keyValueLength = [keyValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - std::string key = std::string([keyValue UTF8String], keyValueLength); - for (auto const &submitKeyEvent : props.traits.submitKeyEvents) { - if ( - submitKeyEvent.key == key && - submitKeyEvent.altKey == [keyEvent[@"altKey"] boolValue] && - submitKeyEvent.shiftKey == [keyEvent[@"shiftKey"] boolValue] && - submitKeyEvent.ctrlKey == [keyEvent[@"ctrlKey"] boolValue] && - submitKeyEvent.metaKey == [keyEvent[@"metaKey"] boolValue] && - submitKeyEvent.functionKey == [keyEvent[@"functionKey"] boolValue] - ) { - shouldSubmit = YES; - break; - } + const char *keyCString = [keyValue UTF8String]; + if (keyCString != nullptr) { + std::string_view key(keyCString); + const bool altKey = [keyEvent[@"altKey"] boolValue]; + const bool shiftKey = [keyEvent[@"shiftKey"] boolValue]; + const bool ctrlKey = [keyEvent[@"ctrlKey"] boolValue]; + const bool metaKey = [keyEvent[@"metaKey"] boolValue]; + const bool functionKey = [keyEvent[@"functionKey"] boolValue]; + + shouldSubmit = std::any_of( + props.traits.submitKeyEvents.begin(), + props.traits.submitKeyEvents.end(), + [&](auto const &submitKeyEvent) { + return submitKeyEvent.key == key && submitKeyEvent.altKey == altKey && + submitKeyEvent.shiftKey == shiftKey && submitKeyEvent.ctrlKey == ctrlKey && + submitKeyEvent.metaKey == metaKey && submitKeyEvent.functionKey == functionKey; + }); } } @@ -648,10 +654,9 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event - (void)textInputDidCancel { if (_eventEmitter) { - auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); textInputEventEmitter.onKeyPress({ - .text = RCTStringFromNSString(@"\x1B"), // Escape key + .text = RCTStringFromNSString(kEscapeKeyCode), .eventCount = static_cast(_mostRecentEventCount), }); } From 4defd8187d68c57bf4ea6f90b8836453b5c90064 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 10:44:55 -0700 Subject: [PATCH 19/24] more fixes --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 2 +- .../react/renderer/components/iostextinput/propsConversions.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 da07f933ab9132..2f1cc93de5e202 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -40,7 +40,7 @@ #endif // [macOS] #if TARGET_OS_OSX // [macOS -static const NSString *kEscapeKeyCode = @"\x1B"; +static NSString *kEscapeKeyCode = @"\x1B"; #endif // macOS] diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h index b1c599f38e4e65..47c96c8dac13f0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/propsConversions.h @@ -223,7 +223,7 @@ inline void fromRawValue( fromRawValue(context, tmp_metaKey->second, result.metaKey); } auto tmp_functionKey = map.find("functionKey"); - if (tmp_functionKey != map.end()) { + if (tmp_functionKey != map.end()) { fromRawValue(context, tmp_functionKey->second, result.functionKey); } } From c7ed2d2a6693c62ec7d58fd7cec60b6634b7d762 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 12:41:29 -0700 Subject: [PATCH 20/24] siimplify scrollview metrics --- .../TextInput/RCTTextInputComponentView.mm | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) 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 2f1cc93de5e202..ff4e497e0a200a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -724,24 +724,7 @@ - (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] #if !TARGET_OS_OSX // [macOS] static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); #else // [macOS - auto metrics = [self _textInputMetrics]; - - CGPoint contentOffset = scrollView.contentOffset; - metrics.contentOffset = {contentOffset.x, contentOffset.y}; - - UIEdgeInsets contentInset = scrollView.contentInset; - metrics.contentInset = {contentInset.left, contentInset.top, contentInset.right, contentInset.bottom}; - - CGSize contentSize = scrollView.contentSize; - metrics.contentSize = {contentSize.width, contentSize.height}; - - CGSize layoutMeasurement = scrollView.bounds.size; - metrics.layoutMeasurement = {layoutMeasurement.width, layoutMeasurement.height}; - - CGFloat zoomScale = scrollView.zoomScale ?: 1; - metrics.zoomScale = zoomScale; - - static_cast(*_eventEmitter).onScroll(metrics); + static_cast(*_eventEmitter).onScroll([self _textInputMetricsWithScrollView:scrollView]); #endif // macOS] } } @@ -940,17 +923,40 @@ - (void)handleInputAccessoryDoneButton .selectionRange = [self _selectionRange], .eventCount = static_cast(_mostRecentEventCount), #if !TARGET_OS_OSX // [macOS] + .contentOffset = Point{}, + .contentInset = EdgeInsets{}, +#else // [macOS .contentOffset = RCTPointFromCGPoint(_backedTextInputView.contentOffset), .contentInset = RCTEdgeInsetsFromUIEdgeInsets(_backedTextInputView.contentInset), -#endif // [macOS] +#endif // macOS] .contentSize = RCTSizeFromCGSize(_backedTextInputView.contentSize), .layoutMeasurement = RCTSizeFromCGSize(_backedTextInputView.bounds.size), #if !TARGET_OS_OSX // [macOS] + .zoomScale = 1, +#else // [macOS .zoomScale = _backedTextInputView.zoomScale, -#endif // [macOS] +#endif // macOS] }; } +#if TARGET_OS_OSX // [macOS +- (TextInputEventEmitter::Metrics)_textInputMetricsWithScrollView:(RCTUIScrollView *)scrollView +{ + TextInputEventEmitter::Metrics metrics = [self _textInputMetrics]; + + if (scrollView) { + metrics.contentOffset = RCTPointFromCGPoint(scrollView.contentOffset); + metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset); + metrics.contentSize = RCTSizeFromCGSize(scrollView.contentSize); + metrics.layoutMeasurement = RCTSizeFromCGSize(scrollView.bounds.size); + metrics.zoomScale = scrollView.zoomScale ?: 1; + } + + return metrics; +} +#endif // macOS] + + - (void)_updateState { if (!_state) { From 412b5d3e3c6d2dab823217796e3a6099b9aeff6a Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 12:53:15 -0700 Subject: [PATCH 21/24] simplify metrics --- .../TextInput/RCTTextInputComponentView.mm | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 ff4e497e0a200a..ac337166c9fd8e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -923,19 +923,15 @@ - (void)handleInputAccessoryDoneButton .selectionRange = [self _selectionRange], .eventCount = static_cast(_mostRecentEventCount), #if !TARGET_OS_OSX // [macOS] - .contentOffset = Point{}, - .contentInset = EdgeInsets{}, -#else // [macOS .contentOffset = RCTPointFromCGPoint(_backedTextInputView.contentOffset), .contentInset = RCTEdgeInsetsFromUIEdgeInsets(_backedTextInputView.contentInset), +#else // [macOS + .contentOffset = {.x = 0, .y = 0}, + .contentInset = EdgeInsets{}, #endif // macOS] .contentSize = RCTSizeFromCGSize(_backedTextInputView.contentSize), .layoutMeasurement = RCTSizeFromCGSize(_backedTextInputView.bounds.size), -#if !TARGET_OS_OSX // [macOS] .zoomScale = 1, -#else // [macOS - .zoomScale = _backedTextInputView.zoomScale, -#endif // macOS] }; } @@ -946,7 +942,7 @@ - (void)handleInputAccessoryDoneButton if (scrollView) { metrics.contentOffset = RCTPointFromCGPoint(scrollView.contentOffset); - metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset); + metrics.contentInse t = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset); metrics.contentSize = RCTSizeFromCGSize(scrollView.contentSize); metrics.layoutMeasurement = RCTSizeFromCGSize(scrollView.bounds.size); metrics.zoomScale = scrollView.zoomScale ?: 1; From 44fee32a52af372b510767c92185a9a3a5bea06b Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 13 Oct 2025 13:31:15 -0700 Subject: [PATCH 22/24] typo --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ac337166c9fd8e..382648a3d54b0c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -942,7 +942,7 @@ - (void)handleInputAccessoryDoneButton if (scrollView) { metrics.contentOffset = RCTPointFromCGPoint(scrollView.contentOffset); - metrics.contentInse t = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset); + metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset); metrics.contentSize = RCTSizeFromCGSize(scrollView.contentSize); metrics.layoutMeasurement = RCTSizeFromCGSize(scrollView.bounds.size); metrics.zoomScale = scrollView.zoomScale ?: 1; From 67580dbc5d7e454701e0644f2148ece9cc93d0bc Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 14 Oct 2025 07:05:11 -0700 Subject: [PATCH 23/24] Update RCTTextInputComponentView.mm Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../TextInput/RCTTextInputComponentView.mm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 382648a3d54b0c..c1a74708b879a4 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -627,13 +627,13 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event const bool metaKey = [keyEvent[@"metaKey"] boolValue]; const bool functionKey = [keyEvent[@"functionKey"] boolValue]; - shouldSubmit = std::any_of( - props.traits.submitKeyEvents.begin(), - props.traits.submitKeyEvents.end(), - [&](auto const &submitKeyEvent) { - return submitKeyEvent.key == key && submitKeyEvent.altKey == altKey && - submitKeyEvent.shiftKey == shiftKey && submitKeyEvent.ctrlKey == ctrlKey && - submitKeyEvent.metaKey == metaKey && submitKeyEvent.functionKey == functionKey; + shouldSubmit = std::any_of( + props.traits.submitKeyEvents.begin(), + props.traits.submitKeyEvents.end(), + [&](auto const &submitKeyEvent) { + return submitKeyEvent.key == key && submitKeyEvent.altKey == altKey && + submitKeyEvent.shiftKey == shiftKey && submitKeyEvent.ctrlKey == ctrlKey && + submitKeyEvent.metaKey == metaKey && submitKeyEvent.functionKey == functionKey; }); } } From de1086d51ad49806c41f1b9aa8d18b085c7c879a Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 14 Oct 2025 07:05:19 -0700 Subject: [PATCH 24/24] Update RCTTextInputComponentView.mm Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c1a74708b879a4..4a237ee7f8227c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -724,7 +724,7 @@ - (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] #if !TARGET_OS_OSX // [macOS] static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); #else // [macOS - static_cast(*_eventEmitter).onScroll([self _textInputMetricsWithScrollView:scrollView]); + static_cast(*_eventEmitter).onScroll([self _textInputMetricsWithScrollView:scrollView]); #endif // macOS] } }