Skip to content

Commit 90ce605

Browse files
authored
feat(fabric): Pick the rest of TextInput changes (#2727)
1 parent fdfba41 commit 90ce605

File tree

10 files changed

+252
-22
lines changed

10 files changed

+252
-22
lines changed

packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ - (instancetype)initWithFrame:(CGRect)frame
6262
selector:@selector(boundsDidChange:)
6363
name:NSViewBoundsDidChangeNotification
6464
object:_scrollView.contentView];
65+
[[NSNotificationCenter defaultCenter] addObserver:self
66+
selector:@selector(scrollViewDidScroll:)
67+
name:NSViewBoundsDidChangeNotification
68+
object:_scrollView.contentView];
6569
}
6670

6771
return self;
@@ -132,6 +136,13 @@ - (void)setTextInputDelegate:(id<RCTBackedTextInputDelegate>)textInputDelegate
132136
#pragma mark -
133137
#pragma mark Scrolling control
134138

139+
#if TARGET_OS_OSX // [macOS
140+
- (void)scrollViewDidScroll:(NSNotification *)notification
141+
{
142+
[self.textInputDelegate scrollViewDidScroll:_scrollView];
143+
}
144+
#endif // macOS]
145+
135146
- (BOOL)scrollEnabled
136147
{
137148
return _scrollView.isScrollEnabled;
@@ -181,6 +192,19 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets
181192
_forwardingTextView.textContainerInsets = textContainerInsets;
182193
}
183194

195+
#pragma mark -
196+
#pragma mark Focus ring
197+
198+
- (BOOL)enableFocusRing
199+
{
200+
return _scrollView.enableFocusRing;
201+
}
202+
203+
- (void)setEnableFocusRing:(BOOL)enableFocusRing
204+
{
205+
_scrollView.enableFocusRing = enableFocusRing;
206+
}
207+
184208
@end
185209

186210
#endif // TARGET_OS_OSX

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,17 +398,17 @@ - (void)textViewDidChangeSelection:(__unused UITextView *)textView
398398
[self textViewProbablyDidChangeSelection];
399399
}
400400

401+
#endif // [macOS]
402+
401403
#pragma mark - UIScrollViewDelegate
402404

403-
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
405+
- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS]
404406
{
405407
if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
406408
[_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView];
407409
}
408410
}
409411

410-
#endif // [macOS]
411-
412412
#if TARGET_OS_OSX // [macOS
413413

414414
#pragma mark - NSTextViewDelegate

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
3939
#else // [macOS
4040
@property (nonatomic, assign) BOOL textWasPasted;
4141
@property (nonatomic, readonly) NSResponder *responder;
42+
@property (nonatomic, assign) BOOL enableFocusRing;
4243
#endif // macOS]
4344
@property (nonatomic, assign, readonly) BOOL dictationRecognizing;
4445
@property (nonatomic, assign) UIEdgeInsets textContainerInset;

packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN
4545
#if !TARGET_OS_OSX // [macOS]
4646
@property (nonatomic, assign, getter=isEditable) BOOL editable;
4747
#else // [macOS
48-
@property (assign, getter=isEditable) BOOL editable;
48+
@property (atomic, assign, getter=isEditable) BOOL editable;
4949
#endif // macOS]
5050
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
5151
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
@@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
5858
#if TARGET_OS_OSX // [macOS
5959
@property (nonatomic, copy, nullable) NSString *text;
6060
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
61-
@property (nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *defaultTextAttributes;
61+
@property (nonatomic, strong, nullable) NSDictionary<NSAttributedStringKey, id> *defaultTextAttributes;
6262
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *typingAttributes;
6363
@property (nonatomic, assign) NSTextAlignment textAlignment;
6464
@property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled;

packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
#import <react/utils/ManagedObjectWrapper.h>
1717
#import "RCTLegacyViewManagerInteropCoordinatorAdapter.h"
1818

19-
#if TARGET_OS_OSX // [macOS
20-
#import <React/RCTView.h>
21-
#endif // macOS]
22-
2319
using namespace facebook::react;
2420

2521
static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView";

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm

Lines changed: 142 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@
1313

1414
#import <React/RCTBackedTextInputViewProtocol.h>
1515
#import <React/RCTScrollViewComponentView.h>
16+
17+
#if !TARGET_OS_OSX // [macOS]
1618
#import <React/RCTUITextField.h>
19+
#else // [macOS
20+
#include <React/RCTUITextField.h>
21+
#include <React/RCTUISecureTextField.h>
22+
#endif // macOS]
23+
1724
#import <React/RCTUITextView.h>
1825
#import <React/RCTUtils.h>
1926
#if TARGET_OS_OSX // [macOS
2027
#import <React/RCTWrappedTextView.h>
28+
#import <React/RCTViewKeyboardEvent.h>
2129
#endif // macOS]
2230

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

42+
#if TARGET_OS_OSX // [macOS
43+
static NSString *kEscapeKeyCode = @"\x1B";
44+
#endif // macOS]
45+
46+
3447
using namespace facebook::react;
3548

3649
@interface RCTTextInputComponentView () <RCTBackedTextInputDelegate, RCTTextInputViewProtocol>
@@ -134,7 +147,10 @@ - (void)didMoveToWindow
134147
if (props.autoFocus) {
135148
#if !TARGET_OS_OSX // [macOS]
136149
[_backedTextInputView becomeFirstResponder];
137-
#endif // [macOS]
150+
#else // [macOS
151+
NSWindow *window = [_backedTextInputView window];
152+
[window makeFirstResponder:_backedTextInputView.responder];
153+
#endif // macOS]
138154
[self scrollCursorIntoView];
139155
}
140156
_didMoveToWindow = YES;
@@ -279,11 +295,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
279295
_backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled;
280296
}
281297

282-
#if !TARGET_OS_OSX // [macOS]
283298
if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) {
299+
#if !TARGET_OS_OSX // [macOS]
284300
_backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry;
301+
#else // [macOS
302+
[self _setSecureTextEntry:newTextInputProps.traits.secureTextEntry];
303+
#endif // macOS]
285304
}
286305

306+
#if !TARGET_OS_OSX // [macOS]
287307
if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) {
288308
_backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType);
289309
}
@@ -557,6 +577,13 @@ - (void)textInputDidChangeSelection
557577
}
558578

559579
#if TARGET_OS_OSX // [macOS
580+
- (void)setEnableFocusRing:(BOOL)enableFocusRing {
581+
[super setEnableFocusRing:enableFocusRing];
582+
if ([_backedTextInputView respondsToSelector:@selector(setEnableFocusRing:)]) {
583+
[_backedTextInputView setEnableFocusRing:enableFocusRing];
584+
}
585+
}
586+
560587
- (void)automaticSpellingCorrectionDidChange:(BOOL)enabled {
561588
if (_eventEmitter) {
562589
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onAutoCorrectChange({.autoCorrectEnabled = static_cast<bool>(enabled)});
@@ -577,9 +604,65 @@ - (void)grammarCheckingDidChange:(BOOL)enabled
577604
}
578605
}
579606

580-
- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event {}
607+
- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event
608+
{
609+
BOOL shouldSubmit = NO;
610+
NSDictionary *keyEvent = [RCTViewKeyboardEvent bodyFromEvent:event];
611+
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
612+
if (props.traits.submitKeyEvents.empty()) {
613+
shouldSubmit = [keyEvent[@"key"] isEqualToString:@"Enter"]
614+
&& ![keyEvent[@"altKey"] boolValue]
615+
&& ![keyEvent[@"shiftKey"] boolValue]
616+
&& ![keyEvent[@"ctrlKey"] boolValue]
617+
&& ![keyEvent[@"metaKey"] boolValue]
618+
&& ![keyEvent[@"functionKey"] boolValue]; // Default clearTextOnSubmit key
619+
} else {
620+
NSString *keyValue = keyEvent[@"key"];
621+
const char *keyCString = [keyValue UTF8String];
622+
if (keyCString != nullptr) {
623+
std::string_view key(keyCString);
624+
const bool altKey = [keyEvent[@"altKey"] boolValue];
625+
const bool shiftKey = [keyEvent[@"shiftKey"] boolValue];
626+
const bool ctrlKey = [keyEvent[@"ctrlKey"] boolValue];
627+
const bool metaKey = [keyEvent[@"metaKey"] boolValue];
628+
const bool functionKey = [keyEvent[@"functionKey"] boolValue];
629+
630+
shouldSubmit = std::any_of(
631+
props.traits.submitKeyEvents.begin(),
632+
props.traits.submitKeyEvents.end(),
633+
[&](auto const &submitKeyEvent) {
634+
return submitKeyEvent.key == key && submitKeyEvent.altKey == altKey &&
635+
submitKeyEvent.shiftKey == shiftKey && submitKeyEvent.ctrlKey == ctrlKey &&
636+
submitKeyEvent.metaKey == metaKey && submitKeyEvent.functionKey == functionKey;
637+
});
638+
}
639+
}
640+
641+
if (shouldSubmit) {
642+
if (_eventEmitter) {
643+
auto const &textInputEventEmitter = *std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter);
644+
textInputEventEmitter.onSubmitEditing([self _textInputMetrics]);
645+
}
581646

582-
- (void)textInputDidCancel {}
647+
if (props.traits.clearTextOnSubmit) {
648+
_backedTextInputView.attributedText = nil;
649+
[self textInputDidChange];
650+
}
651+
}
652+
}
653+
654+
- (void)textInputDidCancel
655+
{
656+
if (_eventEmitter) {
657+
auto const &textInputEventEmitter = *std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter);
658+
textInputEventEmitter.onKeyPress({
659+
.text = RCTStringFromNSString(kEscapeKeyCode),
660+
.eventCount = static_cast<int>(_mostRecentEventCount),
661+
});
662+
}
663+
664+
[self textInputDidEndEditing];
665+
}
583666

584667
- (NSDragOperation)textInputDraggingEntered:(nonnull id<NSDraggingInfo>)draggingInfo {
585668
if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) {
@@ -638,7 +721,11 @@ - (BOOL)textInputShouldHandlePaste:(nonnull id<RCTBackedTextInputViewProtocol>)s
638721
- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS]
639722
{
640723
if (_eventEmitter) {
724+
#if !TARGET_OS_OSX // [macOS]
641725
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onScroll([self _textInputMetrics]);
726+
#else // [macOS
727+
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onScroll([self _textInputMetricsWithScrollView:scrollView]);
728+
#endif // macOS]
642729
}
643730
}
644731

@@ -838,15 +925,34 @@ - (void)handleInputAccessoryDoneButton
838925
#if !TARGET_OS_OSX // [macOS]
839926
.contentOffset = RCTPointFromCGPoint(_backedTextInputView.contentOffset),
840927
.contentInset = RCTEdgeInsetsFromUIEdgeInsets(_backedTextInputView.contentInset),
841-
#endif // [macOS]
928+
#else // [macOS
929+
.contentOffset = {.x = 0, .y = 0},
930+
.contentInset = EdgeInsets{},
931+
#endif // macOS]
842932
.contentSize = RCTSizeFromCGSize(_backedTextInputView.contentSize),
843933
.layoutMeasurement = RCTSizeFromCGSize(_backedTextInputView.bounds.size),
844-
#if !TARGET_OS_OSX // [macOS]
845-
.zoomScale = _backedTextInputView.zoomScale,
846-
#endif // [macOS]
934+
.zoomScale = 1,
847935
};
848936
}
849937

938+
#if TARGET_OS_OSX // [macOS
939+
- (TextInputEventEmitter::Metrics)_textInputMetricsWithScrollView:(RCTUIScrollView *)scrollView
940+
{
941+
TextInputEventEmitter::Metrics metrics = [self _textInputMetrics];
942+
943+
if (scrollView) {
944+
metrics.contentOffset = RCTPointFromCGPoint(scrollView.contentOffset);
945+
metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(scrollView.contentInset);
946+
metrics.contentSize = RCTSizeFromCGSize(scrollView.contentSize);
947+
metrics.layoutMeasurement = RCTSizeFromCGSize(scrollView.bounds.size);
948+
metrics.zoomScale = scrollView.zoomScale ?: 1;
949+
}
950+
951+
return metrics;
952+
}
953+
#endif // macOS]
954+
955+
850956
- (void)_updateState
851957
{
852958
if (!_state) {
@@ -893,6 +999,13 @@ - (void)_restoreTextSelection
893999

8941000
- (void)_setAttributedString:(NSAttributedString *)attributedString
8951001
{
1002+
#if TARGET_OS_OSX // [macOS
1003+
// When the text view displays temporary content (e.g. completions, accents), do not update the attributed string.
1004+
if (_backedTextInputView.hasMarkedText) {
1005+
return;
1006+
}
1007+
#endif // macOS]
1008+
8961009
if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) {
8971010
return;
8981011
}
@@ -1001,6 +1114,27 @@ - (void)_setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus
10011114
}
10021115
#endif // macOS]
10031116

1117+
#if TARGET_OS_OSX // [macOS
1118+
- (void)_setSecureTextEntry:(BOOL)secureTextEntry
1119+
{
1120+
[_backedTextInputView removeFromSuperview];
1121+
RCTPlatformView<RCTBackedTextInputViewProtocol> *backedTextInputView = secureTextEntry ? [RCTUISecureTextField new] : [RCTUITextField new];
1122+
backedTextInputView.frame = _backedTextInputView.frame;
1123+
RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView);
1124+
1125+
// Copy the text field specific properties if we came from a single line input before the switch
1126+
if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) {
1127+
RCTUITextField *previousTextField = (RCTUITextField *)_backedTextInputView;
1128+
RCTUITextField *newTextField = (RCTUITextField *)backedTextInputView;
1129+
newTextField.textAlignment = previousTextField.textAlignment;
1130+
newTextField.text = previousTextField.text;
1131+
}
1132+
1133+
_backedTextInputView = backedTextInputView;
1134+
[self addSubview:_backedTextInputView];
1135+
}
1136+
#endif // macOS]
1137+
10041138
- (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText
10051139
{
10061140
// When the dictation is running we can't update the attributed text on the backed up text view

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizati
3030

3131
UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(
3232
facebook::react::KeyboardAppearance keyboardAppearance);
33-
#endif // [macOS]
3433

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

3836
UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ void RCTCopyBackedTextInput(
2727
toTextInput.placeholder = fromTextInput.placeholder;
2828
toTextInput.placeholderColor = fromTextInput.placeholderColor;
2929
toTextInput.textContainerInset = fromTextInput.textContainerInset;
30+
31+
#if TARGET_OS_OSX // [macOS
32+
toTextInput.accessibilityElement = fromTextInput.accessibilityElement;
33+
toTextInput.accessibilityHelp = fromTextInput.accessibilityHelp;
34+
toTextInput.accessibilityIdentifier = fromTextInput.accessibilityIdentifier;
35+
toTextInput.accessibilityLabel = fromTextInput.accessibilityLabel;
36+
toTextInput.accessibilityRole = fromTextInput.accessibilityRole;
37+
toTextInput.autoresizingMask = fromTextInput.autoresizingMask;
38+
#endif // macOS]
3039
#if TARGET_OS_IOS // [macOS] [visionOS]
3140
toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView;
3241
#endif // [macOS] [visionOS]
@@ -94,9 +103,7 @@ UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppea
94103
return UIKeyboardAppearanceDark;
95104
}
96105
}
97-
#endif // [macOS]
98106

99-
#if !TARGET_OS_OSX // [macOS]
100107
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional<bool> spellCheck)
101108
{
102109
return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo)

0 commit comments

Comments
 (0)