Skip to content

Commit

Permalink
Add smartInsertDelete prop to TextInput component (#36111)
Browse files Browse the repository at this point in the history
Summary:
This PR add support to configure the `smartInsertDeleteType` property in iOS inputs [as described here](https://developer.apple.com/documentation/uikit/uitextinputtraits/2865828-smartinsertdeletetype), making possible toggle this autoformatting behavior.

PR for the docs update: facebook/react-native-website#3560

## Changelog:

[IOS] [ADDED] - Add `smartInsertDelete` prop to `TextInput` component

Pull Request resolved: #36111

Test Plan:
* Added an example in the RNTester app to test this behavior in iOS.
* If the `smartInsertDelete` prop is `true` or `undefined`, the system will use the default autoformatting behavior.
* If the `smartInsertDelete` prop is `false`, the system will disable the autoformatting behavior.

https://user-images.githubusercontent.com/20051562/217862828-70c20344-d687-4824-8f5d-d591eff856ef.mp4

Reviewed By: javache

Differential Revision: D46546277

Pulled By: NickGerleman

fbshipit-source-id: 3172b14fb196876d9ee0030186540608204b96de
  • Loading branch information
fabioh8010 authored and facebook-github-bot committed Jun 8, 2023
1 parent 09e152e commit 6b62f12
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const RCTTextInputViewConfig = {
showSoftInputOnFocus: true,
autoFocus: true,
lineBreakStrategyIOS: true,
smartInsertDelete: true,
...ConditionallyIgnoredEventHandlers({
onChange: true,
onSelectionChange: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ export interface TextInputIOSProps {
| 'hangul-word'
| 'push-out'
| undefined;

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
*
* The default value is `true`.
*/
smartInsertDelete?: boolean | undefined;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,16 @@ type IOSProps = $ReadOnly<{|
* @platform ios
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
*
* The default value is `true`.
*
* @platform ios
*/
smartInsertDelete?: ?boolean,
|}>;

type AndroidProps = $ReadOnly<{|
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,16 @@ type IOSProps = $ReadOnly<{|
* @platform ios
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
*
* The default value is `true`.
*
* @platform ios
*/
smartInsertDelete?: ?boolean,
|}>;

type AndroidProps = $ReadOnly<{|
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/Libraries/Text/RCTConvert+Text.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json;
+ (RCTTextTransform)RCTTextTransform:(nullable id)json;
+ (UITextSmartInsertDeleteType)UITextSmartInsertDeleteType:(nullable id)json;

@end

Expand Down
7 changes: 7 additions & 0 deletions packages/react-native/Libraries/Text/RCTConvert+Text.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ + (UITextSpellCheckingType)UITextSpellCheckingType:(id)json
RCTTextTransformUndefined,
integerValue)

+ (UITextSmartInsertDeleteType)UITextSmartInsertDeleteType:(id)json
{
return json == nil ? UITextSmartInsertDeleteTypeDefault
: [RCTConvert BOOL:json] ? UITextSmartInsertDeleteTypeYes
: UITextSmartInsertDeleteTypeNo;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ @implementation RCTBaseTextInputViewManager {
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL)
RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL)
RCT_REMAP_VIEW_PROPERTY(smartInsertDelete, backedTextInputView.smartInsertDeleteType, UITextSmartInsertDeleteType)
RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(submitBehavior, NSString)
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
_backedTextInputView.passwordRules = RCTUITextInputPasswordRulesFromString(newTextInputProps.traits.passwordRules);
}

if (newTextInputProps.traits.smartInsertDelete != oldTextInputProps.traits.smartInsertDelete) {
if (@available(iOS 11.0, *)) {
_backedTextInputView.smartInsertDeleteType =
RCTUITextSmartInsertDeleteTypeFromOptionalBool(newTextInputProps.traits.smartInsertDelete);
}
}

// Traits `blurOnSubmit`, `clearTextOnFocus`, and `selectTextOnFocus` were omitted intentionally here
// because they are being checked on-demand.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ UITextContentType RCTUITextContentTypeFromString(std::string const &contentType)

UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(std::string const &passwordRules);

UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional<bool> smartInsertDelete);

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ void RCTCopyBackedTextInput(
toTextInput.keyboardType = fromTextInput.keyboardType;
toTextInput.textContentType = fromTextInput.textContentType;

if (@available(iOS 11.0, *)) {
toTextInput.smartInsertDeleteType = fromTextInput.smartInsertDeleteType;
}

toTextInput.passwordRules = fromTextInput.passwordRules;

[toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO];
Expand Down Expand Up @@ -226,3 +230,10 @@ UITextContentType RCTUITextContentTypeFromString(std::string const &contentType)
{
return [UITextInputPasswordRules passwordRulesWithDescriptor:RCTNSStringFromStringNilIfEmpty(passwordRules)];
}

UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional<bool> smartInsertDelete)
{
return smartInsertDelete.has_value()
? (*smartInsertDelete ? UITextSmartInsertDeleteTypeYes : UITextSmartInsertDeleteTypeNo)
: UITextSmartInsertDeleteTypeDefault;
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ class TextInputTraits final {
* Default value: `<empty string>` (no rules).
*/
std::string passwordRules{};

/*
* If `false`, the iOS system will not insert an extra space after a paste
* operation neither delete one or two spaces after a cut or delete operation.
* iOS-only (inherently iOS-specific)
* Can be empty (`null` in JavaScript) which means `default`.
* Default value: `empty` (`null`).
*/
std::optional<bool> smartInsertDelete{};
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ static TextInputTraits convertRawProp(
"passwordRules",
sourceTraits.passwordRules,
defaultTraits.passwordRules);
traits.smartInsertDelete = convertRawProp(
context,
rawProps,
"smartInsertDelete",
sourceTraits.smartInsertDelete,
defaultTraits.smartInsertDelete);

return traits;
}
Expand Down
19 changes: 19 additions & 0 deletions packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -906,4 +906,23 @@ exports.examples = ([
);
},
},
{
title: 'iOS autoformatting behaviors',
render: function (): React.Node {
return (
<View>
<WithLabel label="smartInsertDelete: true | undefined">
<TextInput style={styles.default} defaultValue="CopyAndPaste" />
</WithLabel>
<WithLabel label="smartInsertDelete: false">
<TextInput
smartInsertDelete={false}
style={styles.default}
defaultValue="CopyAndPaste"
/>
</WithLabel>
</View>
);
},
},
]: Array<RNTesterModuleExample>);

0 comments on commit 6b62f12

Please sign in to comment.