diff --git a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js index 72e719c3ea7..eb314f27876 100644 --- a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +++ b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js @@ -121,6 +121,7 @@ const RCTTextInputViewConfig = { }, editable: true, inputAccessoryViewID: true, + inputAccessoryViewButtonLabel: true, caretHidden: true, enablesReturnKeyAutomatically: true, placeholderTextColor: { diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js index ee86dd32cc9..b61fe39916a 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js @@ -266,6 +266,12 @@ type IOSProps = $ReadOnly<{| */ inputAccessoryViewID?: ?string, + /** + * An optional label that overrides the default input accessory view button label. + * @platform ios + */ + inputAccessoryViewButtonLabel?: ?string, + /** * Determines the color of the keyboard. * @platform ios diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 6152b938ede..6f0ff252332 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -310,6 +310,12 @@ type IOSProps = $ReadOnly<{| */ inputAccessoryViewID?: ?string, + /** + * An optional label that overrides the default input accessory view button label. + * @platform ios + */ + inputAccessoryViewButtonLabel?: ?string, + /** * Determines the color of the keyboard. * @platform ios diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h index 205f9943262..e176ff39850 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL caretHidden; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; +@property (nonatomic, strong, nullable) NSString *inputAccessoryViewButtonLabel; @end diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index cc510133cc6..e3df41a87f3 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UITextFieldViewMode clearButtonMode; @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; +@property (nonatomic, strong, nullable) NSString *inputAccessoryViewButtonLabel; @property (nonatomic, assign, readonly) CGFloat zoomScale; @property (nonatomic, assign, readonly) CGPoint contentOffset; @property (nonatomic, assign, readonly) UIEdgeInsets contentInset; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h index 9a02b822496..48046242d7e 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h @@ -49,6 +49,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) NSNumber *maxLength; @property (nonatomic, copy, nullable) NSAttributedString *attributedText; @property (nonatomic, copy) NSString *inputAccessoryViewID; +@property (nonatomic, strong) NSString *inputAccessoryViewButtonLabel; @property (nonatomic, assign) UIKeyboardType keyboardType; @property (nonatomic, assign) BOOL showSoftInputOnFocus; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index b9c699b8daa..95edfd512fe 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -384,6 +384,16 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) } } +- (NSString *)inputAccessoryViewButtonLabel +{ + return self.backedTextInputView.inputAccessoryViewButtonLabel; +} + +- (void)setInputAccessoryViewButtonLabel:(NSString *)inputAccessoryViewButtonLabel +{ + self.backedTextInputView.inputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel; +} + #pragma mark - RCTBackedTextInputDelegate - (BOOL)textInputShouldBeginEditing @@ -715,6 +725,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) { UIView *textInputView = self.backedTextInputView; UIKeyboardType keyboardType = textInputView.keyboardType; + NSString *inputAccessoryViewButtonLabel = textInputView.inputAccessoryViewButtonLabel; // These keyboard types (all are number pads) don't have a Return Key button by default, // so we create an `inputAccessoryView` with this button for them. @@ -722,11 +733,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) UIReturnKeyType returnKeyType = textInputView.returnKeyType; BOOL containsKeyType = [returnKeyTypesSet containsObject:@(returnKeyType)]; + BOOL containsInputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel != nil; BOOL shouldHaveInputAccessoryView = (keyboardType == UIKeyboardTypeNumberPad || keyboardType == UIKeyboardTypePhonePad || keyboardType == UIKeyboardTypeDecimalPad || keyboardType == UIKeyboardTypeASCIICapableNumberPad) && - containsKeyType; + (containsKeyType || containsInputAccessoryViewButtonLabel); if (_hasInputAccessoryView == shouldHaveInputAccessoryView) { return; @@ -735,7 +747,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) _hasInputAccessoryView = shouldHaveInputAccessoryView; if (shouldHaveInputAccessoryView) { - NSString *buttonLabel = [self returnKeyTypeToString:returnKeyType]; + NSString *buttonLabel = inputAccessoryViewButtonLabel != nil ? inputAccessoryViewButtonLabel + : [self returnKeyTypeToString:returnKeyType]; UIToolbar *toolbarView = [UIToolbar new]; [toolbarView sizeToFit]; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm index 20749a60288..9a010a3d182 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm @@ -57,6 +57,7 @@ RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL) RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection) RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString) +RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewButtonLabel, NSString) RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString) RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString) diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h index 91f8eb087ac..13c0b229eb5 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, getter=isEditable) BOOL editable; @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; +@property (nonatomic, strong, nullable) NSString *inputAccessoryViewButtonLabel; @property (nonatomic, assign, readonly) CGFloat zoomScale; @property (nonatomic, assign, readonly) CGPoint contentOffset; @property (nonatomic, assign, readonly) UIEdgeInsets contentInset; diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 01ea39f9f60..2639b859abe 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -2962,6 +2962,7 @@ type IOSProps = $ReadOnly<{| | $ReadOnlyArray, enablesReturnKeyAutomatically?: ?boolean, inputAccessoryViewID?: ?string, + inputAccessoryViewButtonLabel?: ?string, keyboardAppearance?: ?(\\"default\\" | \\"light\\" | \\"dark\\"), passwordRules?: ?PasswordRules, rejectResponderTermination?: ?boolean, @@ -3314,6 +3315,7 @@ type IOSProps = $ReadOnly<{| | $ReadOnlyArray, enablesReturnKeyAutomatically?: ?boolean, inputAccessoryViewID?: ?string, + inputAccessoryViewButtonLabel?: ?string, keyboardAppearance?: ?(\\"default\\" | \\"light\\" | \\"dark\\"), passwordRules?: ?PasswordRules, rejectResponderTermination?: ?boolean, 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 01a066d3c51..f9df6a01226 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -273,6 +273,11 @@ static NSSet *returnKeyTypesSet; if (newTextInputProps.inputAccessoryViewID != oldTextInputProps.inputAccessoryViewID) { _backedTextInputView.inputAccessoryViewID = RCTNSStringFromString(newTextInputProps.inputAccessoryViewID); } + + if (newTextInputProps.inputAccessoryViewButtonLabel != oldTextInputProps.inputAccessoryViewButtonLabel) { + _backedTextInputView.inputAccessoryViewButtonLabel = + RCTNSStringFromString(newTextInputProps.inputAccessoryViewButtonLabel); + } [super updateProps:props oldProps:oldProps]; [self setDefaultInputAccessoryView]; @@ -581,22 +586,25 @@ static NSSet *returnKeyTypesSet; UIKeyboardType keyboardType = _backedTextInputView.keyboardType; UIReturnKeyType returnKeyType = _backedTextInputView.returnKeyType; + NSString *inputAccessoryViewButtonLabel = _backedTextInputView.inputAccessoryViewButtonLabel; BOOL containsKeyType = [returnKeyTypesSet containsObject:@(returnKeyType)]; + BOOL containsInputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel != nil; // These keyboard types (all are number pads) don't have a "returnKey" button by default, // so we create an `inputAccessoryView` with this button for them. BOOL shouldHaveInputAccessoryView = (keyboardType == UIKeyboardTypeNumberPad || keyboardType == UIKeyboardTypePhonePad || keyboardType == UIKeyboardTypeDecimalPad || keyboardType == UIKeyboardTypeASCIICapableNumberPad) && - containsKeyType; + (containsKeyType || containsInputAccessoryViewButtonLabel); if ((_backedTextInputView.inputAccessoryView != nil) == shouldHaveInputAccessoryView) { return; } if (shouldHaveInputAccessoryView) { - NSString *buttonLabel = [self returnKeyTypeToString:returnKeyType]; + NSString *buttonLabel = inputAccessoryViewButtonLabel != nil ? inputAccessoryViewButtonLabel + : [self returnKeyTypeToString:returnKeyType]; UIToolbar *toolbarView = [UIToolbar new]; [toolbarView sizeToFit]; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.cpp index ac858630490..9d7edc03085 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.cpp @@ -33,6 +33,12 @@ TextInputProps::TextInputProps( "inputAccessoryViewID", sourceProps.inputAccessoryViewID, {})), + inputAccessoryViewButtonLabel(convertRawProp( + context, + rawProps, + "inputAccessoryViewButtonLabel", + sourceProps.inputAccessoryViewButtonLabel, + {})), onKeyPressSync(convertRawProp( context, rawProps, diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.h index 449429efc85..95caad124b4 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputProps.h @@ -37,6 +37,7 @@ class TextInputProps final : public BaseTextInputProps { std::optional selection{}; const std::string inputAccessoryViewID{}; + const std::string inputAccessoryViewButtonLabel{}; bool onKeyPressSync{false}; bool onChangeSync{false}; diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index 30b8a5dc815..f9018a788f0 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -349,6 +349,27 @@ const textInputExamples: Array = [ return {examples}; }, }, + { + title: 'Custom Input Accessory View Button Label', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, { title: 'Nested content and `value` property', render: function (): React.Node {