Skip to content

Commit

Permalink
feat: Manage keyboard shortcuts visibility of TextInput (#47671)
Browse files Browse the repository at this point in the history
Summary:
**iOS** does offer a native property for **UITextField** called `inputAssistantItem`. According to the [documentation](https://developer.apple.com/documentation/uikit/uitextinputassistantitem), we can hide the **"shortcuts"** by setting the `leadingBarButtonGroups` and `trailingBarButtonGroups` properties to empty arrays.

I propose adding a new property for **TextInput** in **React Native**, which would set these native properties to empty arrays. This new property could be called `disableInputAssistant` or `disableKeyboardShortcuts` and would be a `boolean`.

Developers can manage this behavior (the redo & undo buttons and suggestions pop-up hiding) after applying these native props.

react-native-community/discussions-and-proposals#830

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[IOS] [ADDED] - [TextInput] Integrate a new property - `disableKeyboardShortcuts`. It can disable the keyboard shortcuts on iPads.

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[IOS] [ADDED] - [TextInput] Integrate a new property - `disableKeyboardShortcuts`. It can disable the keyboard shortcuts on iPads.

Pull Request resolved: #47671

Test Plan:
Manual
1. Open TextInput examples.
2. Scroll down and reach the "Keyboard shortcuts" section.
3. Test each case.

Note: **TextInput** behaves the same as now when the new prop is not passed or is `false`.

https://github.com/user-attachments/assets/5e814516-9e6c-4495-9d46-8175425c4456

Reviewed By: javache

Differential Revision: D67451609

Pulled By: cipolleschi

fbshipit-source-id: 59ba3a5cc1644ed176420f82dc98232d88341c6e
  • Loading branch information
rezkiy37 authored and facebook-github-bot committed Jan 6, 2025
1 parent a3dfc49 commit 0154372
Show file tree
Hide file tree
Showing 17 changed files with 186 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ const RCTTextInputViewConfig = {
onChangeSync: true,
onKeyPressSync: true,
}),
disableKeyboardShortcuts: true,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ export interface DocumentSelectionState extends EventEmitter {
* @see https://reactnative.dev/docs/textinput#props
*/
export interface TextInputIOSProps {
/**
* If true, the keyboard shortcuts (undo/redo and copy buttons) are disabled. The default value is false.
*/
disableKeyboardShortcuts?: boolean | undefined;

/**
* enum('never', 'while-editing', 'unless-editing', 'always')
* When the clear button should appear on the right side of the text view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ export type enterKeyHintType =
type PasswordRules = string;

type IOSProps = $ReadOnly<{|
/**
* If true, the keyboard shortcuts (undo/redo and copy buttons) are disabled. The default value is false.
* @platform ios
*/
disableKeyboardShortcuts?: ?boolean,

/**
* When the clear button should appear on the right side of the text view.
* This property is supported only for single-line TextInput component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ export type enterKeyHintType =
type PasswordRules = string;

type IOSProps = $ReadOnly<{|
/**
* If true, the keyboard shortcuts (undo/redo and copy buttons) are disabled. The default value is false.
* @platform ios
*/
disableKeyboardShortcuts?: ?boolean,

/**
* When the clear button should appear on the right side of the text view.
* This property is supported only for single-line TextInput component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewButtonLabel;

@property (nonatomic, assign) BOOL disableKeyboardShortcuts;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ @implementation RCTUITextView {
UITextView *_detachedTextView;
RCTBackedTextViewDelegateAdapter *_textInputDelegateAdapter;
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
}

static UIFont *defaultPlaceholderFont(void)
Expand Down Expand Up @@ -52,6 +54,8 @@ - (instancetype)initWithFrame:(CGRect)frame
self.textContainer.lineFragmentPadding = 0;
self.scrollsToTop = NO;
self.scrollEnabled = YES;
_initialValueLeadingBarButtonGroups = nil;
_initialValueTrailingBarButtonGroups = nil;
}

return self;
Expand Down Expand Up @@ -132,6 +136,26 @@ - (void)textDidChange
[self _invalidatePlaceholderVisibility];
}

- (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts
{
// Initialize the initial values only once
if (_initialValueLeadingBarButtonGroups == nil) {
// Capture initial values of leading and trailing button groups
_initialValueLeadingBarButtonGroups = self.inputAssistantItem.leadingBarButtonGroups;
_initialValueTrailingBarButtonGroups = self.inputAssistantItem.trailingBarButtonGroups;
}

if (disableKeyboardShortcuts) {
self.inputAssistantItem.leadingBarButtonGroups = @[];
self.inputAssistantItem.trailingBarButtonGroups = @[];
} else {
// Restore the initial values
self.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups;
self.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups;
}
_disableKeyboardShortcuts = disableKeyboardShortcuts;
}

#pragma mark - Overrides

- (void)setFont:(UIFont *)font
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ NS_ASSUME_NONNULL_BEGIN
// Use `attributedText.string` instead.
@property (nonatomic, copy, nullable) NSString *text NS_UNAVAILABLE;

@property (nonatomic, assign) BOOL disableKeyboardShortcuts;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ @implementation RCTBaseTextInputView {
BOOL _hasInputAccessoryView;
NSString *_Nullable _predictedText;
BOOL _didMoveToWindow;
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
}

- (void)reactUpdateResponderOffsetForScrollView:(RCTScrollView *)scrollView
Expand Down Expand Up @@ -65,6 +67,8 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_bridge = bridge;
_eventDispatcher = bridge.eventDispatcher;
[self initializeReturnKeyType];
_initialValueLeadingBarButtonGroups = nil;
_initialValueTrailingBarButtonGroups = nil;
}

return self;
Expand Down Expand Up @@ -394,6 +398,25 @@ - (void)setInputAccessoryViewButtonLabel:(NSString *)inputAccessoryViewButtonLab
self.backedTextInputView.inputAccessoryViewButtonLabel = inputAccessoryViewButtonLabel;
}

- (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts
{
// Initialize the initial values only once
if (_initialValueLeadingBarButtonGroups == nil) {
// Capture initial values of leading and trailing button groups
_initialValueLeadingBarButtonGroups = self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups;
_initialValueTrailingBarButtonGroups = self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups;
}

if (disableKeyboardShortcuts) {
self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups = @[];
self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups = @[];
} else {
// Restore the initial values
self.backedTextInputView.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups;
self.backedTextInputView.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups;
}
}

#pragma mark - RCTBackedTextInputDelegate

- (BOOL)textInputShouldBeginEditing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ @implementation RCTBaseTextInputViewManager {

RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)

RCT_EXPORT_VIEW_PROPERTY(disableKeyboardShortcuts, BOOL)

RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, readonly) CGFloat zoomScale;
@property (nonatomic, assign, readonly) CGPoint contentOffset;
@property (nonatomic, assign, readonly) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL disableKeyboardShortcuts;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
@implementation RCTUITextField {
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand All @@ -27,6 +29,8 @@ - (instancetype)initWithFrame:(CGRect)frame

_textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self];
_scrollEnabled = YES;
_initialValueLeadingBarButtonGroups = nil;
_initialValueTrailingBarButtonGroups = nil;
}

return self;
Expand Down Expand Up @@ -115,6 +119,26 @@ - (void)setSecureTextEntry:(BOOL)secureTextEntry
self.attributedText = originalText;
}

- (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts
{
// Initialize the initial values only once
if (_initialValueLeadingBarButtonGroups == nil) {
// Capture initial values of leading and trailing button groups
_initialValueLeadingBarButtonGroups = self.inputAssistantItem.leadingBarButtonGroups;
_initialValueTrailingBarButtonGroups = self.inputAssistantItem.trailingBarButtonGroups;
}

if (disableKeyboardShortcuts) {
self.inputAssistantItem.leadingBarButtonGroups = @[];
self.inputAssistantItem.trailingBarButtonGroups = @[];
} else {
// Restore the initial values
self.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups;
self.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups;
}
_disableKeyboardShortcuts = disableKeyboardShortcuts;
}

#pragma mark - Placeholder

- (NSDictionary<NSAttributedStringKey, id> *)_placeholderTextAttributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3026,6 +3026,7 @@ export type enterKeyHintType =
| \\"send\\";
type PasswordRules = string;
type IOSProps = $ReadOnly<{|
disableKeyboardShortcuts?: ?boolean,
clearButtonMode?: ?(\\"never\\" | \\"while-editing\\" | \\"unless-editing\\" | \\"always\\"),
clearTextOnFocus?: ?boolean,
dataDetectorTypes?:
Expand Down Expand Up @@ -3379,6 +3380,7 @@ export type enterKeyHintType =
| \\"enter\\";
type PasswordRules = string;
type IOSProps = $ReadOnly<{|
disableKeyboardShortcuts?: ?boolean,
clearButtonMode?: ?(\\"never\\" | \\"while-editing\\" | \\"unless-editing\\" | \\"always\\"),
clearTextOnFocus?: ?boolean,
dataDetectorTypes?:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
_backedTextInputView.inputAccessoryViewButtonLabel =
RCTNSStringFromString(newTextInputProps.inputAccessoryViewButtonLabel);
}

if (newTextInputProps.disableKeyboardShortcuts != oldTextInputProps.disableKeyboardShortcuts) {
_backedTextInputView.disableKeyboardShortcuts = newTextInputProps.disableKeyboardShortcuts;
}

[super updateProps:props oldProps:oldProps];

[self setDefaultInputAccessoryView];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void RCTCopyBackedTextInput(
toTextInput.textContentType = fromTextInput.textContentType;
toTextInput.smartInsertDeleteType = fromTextInput.smartInsertDeleteType;
toTextInput.passwordRules = fromTextInput.passwordRules;
toTextInput.disableKeyboardShortcuts = fromTextInput.disableKeyboardShortcuts;

[toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ BaseTextInputProps::BaseTextInputProps(
rawProps,
"multiline",
sourceProps.multiline,
{false})),
disableKeyboardShortcuts(convertRawProp(
context,
rawProps,
"disableKeyboardShortcuts",
sourceProps.disableKeyboardShortcuts,
{false})) {}

void BaseTextInputProps::setProp(
Expand Down Expand Up @@ -208,6 +214,7 @@ void BaseTextInputProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(readOnly);
RAW_SET_PROP_SWITCH_CASE_BASIC(submitBehavior);
RAW_SET_PROP_SWITCH_CASE_BASIC(multiline);
RAW_SET_PROP_SWITCH_CASE_BASIC(disableKeyboardShortcuts);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class BaseTextInputProps : public ViewProps, public BaseTextProps {
SubmitBehavior submitBehavior{SubmitBehavior::Default};

bool multiline{false};

bool disableKeyboardShortcuts{false};
};

} // namespace facebook::react
73 changes: 73 additions & 0 deletions packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,73 @@ const TextInputWithFocusButton = () => {
);
};

function KeyboardShortcutsExample() {
return (
<View>
<Text
style={{
marginBottom: 4,
}}>
Single line:
</Text>

<ExampleTextInput
style={{
marginBottom: 4,
}}
placeholder="Default"
/>

<ExampleTextInput
style={{
marginBottom: 4,
}}
placeholder="Disable keyboard shortcuts"
disableKeyboardShortcuts
/>

<ExampleTextInput
style={{
marginBottom: 4,
}}
placeholder="Hidden keyboard with suggestions"
showSoftInputOnFocus={false}
/>

<ExampleTextInput
style={{
marginBottom: 4,
}}
placeholder="Hidden keyboard without suggestions"
disableKeyboardShortcuts
autoCorrect={false}
spellCheck={false}
showSoftInputOnFocus={false}
/>

<Text
style={{
marginBottom: 4,
}}>
Multiline:
</Text>

<ExampleTextInput
style={styles.multiline}
multiline
placeholder="default"
/>

<ExampleTextInput
style={styles.multiline}
multiline
placeholder="Disable keyboard shortcuts"
disableKeyboardShortcuts
/>
</View>
);
}

const styles = StyleSheet.create({
multiline: {
height: 50,
Expand Down Expand Up @@ -938,6 +1005,12 @@ const textInputExamples: Array<RNTesterModuleExample> = [
return <TextInputWithFocusButton />;
},
},
{
title: 'Keyboard shortcuts',
render: function (): React.Node {
return <KeyboardShortcutsExample />;
},
},
{
title: 'Line Break Mode',
render: function (): React.Node {
Expand Down

0 comments on commit 0154372

Please sign in to comment.