Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6daef14
[fabric] Add wrapper class for TextView with scroll callback support
Dec 7, 2023
0994e37
slight modifications
Saadnajmi Oct 10, 2025
c7ecd3c
[fabric] Add responder property to backing text input view protocol
Dec 7, 2023
11ab6cd
[fabric] Use wrapped text view for multiline TextInput
Dec 7, 2023
dadf2de
[fabric] Support showing/hiding the focus ring for TextInput
Dec 9, 2023
a03e759
[fabric] Implement escape/cancel key press callback for TextInput
Dec 9, 2023
e5f5425
[fabric] Submit scroll view metrics when scrolling multiline TextInput
Dec 9, 2023
5df9acd
remove odd ifdef
Saadnajmi Oct 10, 2025
20c8212
[fabric] Fix random TextInput cursor position changes while typing
Jan 15, 2024
113fe8a
[fabric] Add submitKeyEvents property to TextInput
Jan 18, 2024
dcfe453
[fabric] Implement TextInput key down event checking for submit
Jan 18, 2024
7311f83
[fabric] Add support for clearing the TextInput on submit
Jan 18, 2024
bd7408b
[fabric] Add support for the secure text entry to TextInput
Jan 19, 2024
3275685
[fabric] Copy accessibility attributes when switching TextInput backi…
Jan 19, 2024
5f3faf6
[fabric] TextInput should get focus with `autoFocus` prop
shwanton Mar 13, 2024
c1df4ad
[fabric] Fix warning & formatting
shwanton May 1, 2024
484018e
Merge branch 'main' into more-2
Saadnajmi Oct 13, 2025
84f6f50
build fixes
Saadnajmi Oct 13, 2025
0000b9c
PR feedback + std::any_of
Saadnajmi Oct 13, 2025
4defd81
more fixes
Saadnajmi Oct 13, 2025
c7ed2d2
siimplify scrollview metrics
Saadnajmi Oct 13, 2025
412b5d3
simplify metrics
Saadnajmi Oct 13, 2025
44fee32
typo
Saadnajmi Oct 13, 2025
67580db
Update RCTTextInputComponentView.mm
Saadnajmi Oct 14, 2025
de1086d
Update RCTTextInputComponentView.mm
Saadnajmi Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ - (instancetype)initWithFrame:(CGRect)frame
selector:@selector(boundsDidChange:)
name:NSViewBoundsDidChangeNotification
object:_scrollView.contentView];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(scrollViewDidScroll:)
name:NSViewBoundsDidChangeNotification
object:_scrollView.contentView];
}

return self;
Expand Down Expand Up @@ -132,6 +136,13 @@ - (void)setTextInputDelegate:(id<RCTBackedTextInputDelegate>)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;
Expand Down Expand Up @@ -181,6 +192,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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<NSAttributedStringKey, id> *defaultTextAttributes;
@property (nonatomic, strong, nullable) NSDictionary<NSAttributedStringKey, id> *defaultTextAttributes;
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *typingAttributes;
@property (nonatomic, assign) NSTextAlignment textAlignment;
@property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
#import <react/utils/ManagedObjectWrapper.h>
#import "RCTLegacyViewManagerInteropCoordinatorAdapter.h"

#if TARGET_OS_OSX // [macOS
#import <React/RCTView.h>
#endif // macOS]

using namespace facebook::react;

static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@

#import <React/RCTBackedTextInputViewProtocol.h>
#import <React/RCTScrollViewComponentView.h>

#if !TARGET_OS_OSX // [macOS]
#import <React/RCTUITextField.h>
#else // [macOS
#include <React/RCTUITextField.h>
#include <React/RCTUISecureTextField.h>
#endif // macOS]

#import <React/RCTUITextView.h>
#import <React/RCTUtils.h>
#if TARGET_OS_OSX // [macOS
#import <React/RCTWrappedTextView.h>
#import <React/RCTViewKeyboardEvent.h>
#endif // macOS]

#import "RCTConversions.h"
Expand All @@ -31,6 +39,11 @@
static const CGFloat kSingleLineKeyboardBottomOffset = 15.0;
#endif // [macOS]

#if TARGET_OS_OSX // [macOS
static NSString *kEscapeKeyCode = @"\x1B";
#endif // macOS]


using namespace facebook::react;

@interface RCTTextInputComponentView () <RCTBackedTextInputDelegate, RCTTextInputViewProtocol>
Expand Down Expand Up @@ -134,7 +147,10 @@ - (void)didMoveToWindow
if (props.autoFocus) {
#if !TARGET_OS_OSX // [macOS]
[_backedTextInputView becomeFirstResponder];
#endif // [macOS]
#else // [macOS
NSWindow *window = [_backedTextInputView window];
[window makeFirstResponder:_backedTextInputView.responder];
#endif // macOS]
[self scrollCursorIntoView];
}
_didMoveToWindow = YES;
Expand Down Expand Up @@ -279,11 +295,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);
}
Expand Down Expand Up @@ -557,6 +577,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<TextInputEventEmitter const>(_eventEmitter)->onAutoCorrectChange({.autoCorrectEnabled = static_cast<bool>(enabled)});
Expand All @@ -577,9 +604,65 @@ - (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<TextInputProps const>(_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"];
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;
});
}
}

if (shouldSubmit) {
if (_eventEmitter) {
auto const &textInputEventEmitter = *std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter);
textInputEventEmitter.onSubmitEditing([self _textInputMetrics]);
}

- (void)textInputDidCancel {}
if (props.traits.clearTextOnSubmit) {
_backedTextInputView.attributedText = nil;
[self textInputDidChange];
}
}
}

- (void)textInputDidCancel
{
if (_eventEmitter) {
auto const &textInputEventEmitter = *std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter);
textInputEventEmitter.onKeyPress({
.text = RCTStringFromNSString(kEscapeKeyCode),
.eventCount = static_cast<int>(_mostRecentEventCount),
});
}

[self textInputDidEndEditing];
}

- (NSDragOperation)textInputDraggingEntered:(nonnull id<NSDraggingInfo>)draggingInfo {
if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) {
Expand Down Expand Up @@ -638,7 +721,11 @@ - (BOOL)textInputShouldHandlePaste:(nonnull id<RCTBackedTextInputViewProtocol>)s
- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS]
{
if (_eventEmitter) {
#if !TARGET_OS_OSX // [macOS]
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onScroll([self _textInputMetrics]);
#else // [macOS
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onScroll([self _textInputMetricsWithScrollView:scrollView]);
#endif // macOS]
}
}

Expand Down Expand Up @@ -838,15 +925,34 @@ - (void)handleInputAccessoryDoneButton
#if !TARGET_OS_OSX // [macOS]
.contentOffset = RCTPointFromCGPoint(_backedTextInputView.contentOffset),
.contentInset = RCTEdgeInsetsFromUIEdgeInsets(_backedTextInputView.contentInset),
#endif // [macOS]
#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 = _backedTextInputView.zoomScale,
#endif // [macOS]
.zoomScale = 1,
};
}

#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) {
Expand Down Expand Up @@ -893,6 +999,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;
}
Expand Down Expand Up @@ -1001,6 +1114,27 @@ - (void)_setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus
}
#endif // macOS]

#if TARGET_OS_OSX // [macOS
- (void)_setSecureTextEntry:(BOOL)secureTextEntry
{
[_backedTextInputView removeFromSuperview];
RCTPlatformView<RCTBackedTextInputViewProtocol> *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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizati

UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(
facebook::react::KeyboardAppearance keyboardAppearance);
#endif // [macOS]

#if !TARGET_OS_OSX // [macOS]
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional<bool> spellCheck);

UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ void RCTCopyBackedTextInput(
toTextInput.placeholder = fromTextInput.placeholder;
toTextInput.placeholderColor = fromTextInput.placeholderColor;
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]
toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView;
#endif // [macOS] [visionOS]
Expand Down Expand Up @@ -94,9 +103,7 @@ UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppea
return UIKeyboardAppearanceDark;
}
}
#endif // [macOS]

#if !TARGET_OS_OSX // [macOS]
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional<bool> spellCheck)
{
return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo)
Expand Down
Loading
Loading