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"
3139static 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+
3447using 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
0 commit comments