first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTViewComponentView.h>
NS_ASSUME_NONNULL_BEGIN
/**
* UIView class for <TextInput> component.
*/
@interface RCTTextInputComponentView : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,889 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTextInputComponentView.h"
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/components/iostextinput/TextInputComponentDescriptor.h>
#import <react/renderer/textlayoutmanager/RCTAttributedTextUtils.h>
#import <react/renderer/textlayoutmanager/TextLayoutManager.h>
#import <React/RCTBackedTextInputViewProtocol.h>
#import <React/RCTScrollViewComponentView.h>
#import <React/RCTUITextField.h>
#import <React/RCTUITextView.h>
#import <React/RCTUtils.h>
#import "RCTConversions.h"
#import "RCTTextInputNativeCommands.h"
#import "RCTTextInputUtils.h"
#import <limits>
#import "RCTFabricComponentsPlugins.h"
/** Native iOS text field bottom keyboard offset amount */
static const CGFloat kSingleLineKeyboardBottomOffset = 15.0;
using namespace facebook::react;
@interface RCTTextInputComponentView () <
RCTBackedTextInputDelegate,
RCTTextInputViewProtocol,
UIDropInteractionDelegate>
@end
static NSSet<NSNumber *> *returnKeyTypesSet;
@implementation RCTTextInputComponentView {
TextInputShadowNode::ConcreteState::Shared _state;
UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
NSUInteger _mostRecentEventCount;
NSAttributedString *_lastStringStateWasUpdatedWith;
/*
* UIKit uses either UITextField or UITextView as its UIKit element for <TextInput>. UITextField is for single line
* entry, UITextView is for multiline entry. There is a problem with order of events when user types a character. In
* UITextField (single line text entry), typing a character first triggers `onChange` event and then
* onSelectionChange. In UITextView (multi line text entry), typing a character first triggers `onSelectionChange` and
* then onChange. JavaScript depends on `onChange` to be called before `onSelectionChange`. This flag keeps state so
* if UITextView is backing text input view, inside `-[RCTTextInputComponentView textInputDidChangeSelection]` we make
* sure to call `onChange` before `onSelectionChange` and ignore next `-[RCTTextInputComponentView
* textInputDidChange]` call.
*/
BOOL _ignoreNextTextInputCall;
/*
* A flag that when set to true, `_mostRecentEventCount` won't be incremented when `[self _updateState]`
* and delegate methods `textInputDidChange` and `textInputDidChangeSelection` will exit early.
*
* Setting `_backedTextInputView.attributedText` triggers delegate methods `textInputDidChange` and
* `textInputDidChangeSelection` for multiline text input only.
* In multiline text input this is undesirable as we don't want to be sending events for changes that JS triggered.
*/
BOOL _comingFromJS;
BOOL _didMoveToWindow;
/*
* Newly initialized default typing attributes contain a no-op NSParagraphStyle and NSShadow. These cause inequality
* between the AttributedString backing the input and those generated from state. We store these attributes to make
* later comparison insensitive to them.
*/
NSDictionary<NSAttributedStringKey, id> *_originalTypingAttributes;
BOOL _hasInputAccessoryView;
CGSize _previousContentSize;
}
#pragma mark - UIView overrides
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
const auto &defaultProps = TextInputShadowNode::defaultSharedProps();
_props = defaultProps;
_backedTextInputView = defaultProps->multiline ? [RCTUITextView new] : [RCTUITextField new];
_backedTextInputView.textInputDelegate = self;
_ignoreNextTextInputCall = NO;
_comingFromJS = NO;
_didMoveToWindow = NO;
_originalTypingAttributes = [_backedTextInputView.typingAttributes copy];
_previousContentSize = CGSizeZero;
[self addSubview:_backedTextInputView];
[self initializeReturnKeyType];
}
return self;
}
- (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
{
[super updateEventEmitter:eventEmitter];
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
[_backedTextInputView.defaultTextAttributes mutableCopy];
defaultAttributes[RCTAttributedStringEventEmitterKey] = RCTWrapEventEmitter(_eventEmitter);
_backedTextInputView.defaultTextAttributes = defaultAttributes;
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (self.window && !_didMoveToWindow) {
const auto &props = static_cast<const TextInputProps &>(*_props);
if (props.autoFocus) {
[_backedTextInputView becomeFirstResponder];
[self scrollCursorIntoView];
}
_didMoveToWindow = YES;
[self initializeReturnKeyType];
}
[self _restoreTextSelection];
}
// TODO: replace with registerForTraitChanges once iOS 17.0 is the lowest supported version
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (facebook::react::ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() &&
UITraitCollection.currentTraitCollection.preferredContentSizeCategory !=
previousTraitCollection.preferredContentSizeCategory) {
const auto &newTextInputProps = static_cast<const TextInputProps &>(*_props);
_backedTextInputView.defaultTextAttributes =
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
}
}
- (void)reactUpdateResponderOffsetForScrollView:(RCTScrollViewComponentView *)scrollView
{
if (![self isDescendantOfView:scrollView.scrollView] || !_backedTextInputView.isFirstResponder) {
// View is outside scroll view or it's not a first responder.
scrollView.firstResponderViewOutsideScrollView = _backedTextInputView;
return;
}
UITextRange *selectedTextRange = _backedTextInputView.selectedTextRange;
UITextSelectionRect *selection = [_backedTextInputView selectionRectsForRange:selectedTextRange].firstObject;
CGRect focusRect;
if (selection == nil) {
// No active selection or caret - fallback to entire input frame
focusRect = self.bounds;
} else {
// Focus on text selection frame
focusRect = selection.rect;
BOOL isMultiline = [_backedTextInputView isKindOfClass:[UITextView class]];
if (!isMultiline) {
focusRect.size.height += kSingleLineKeyboardBottomOffset;
}
}
scrollView.firstResponderFocus = [self convertRect:focusRect toView:nil];
}
#pragma mark - RCTViewComponentView overrides
- (NSObject *)accessibilityElement
{
return _backedTextInputView;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<TextInputComponentDescriptor>();
}
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
{
const auto &oldTextInputProps = static_cast<const TextInputProps &>(*_props);
const auto &newTextInputProps = static_cast<const TextInputProps &>(*props);
// Traits:
if (newTextInputProps.multiline != oldTextInputProps.multiline) {
[self _setMultiline:newTextInputProps.multiline];
}
if (newTextInputProps.traits.autocapitalizationType != oldTextInputProps.traits.autocapitalizationType) {
_backedTextInputView.autocapitalizationType =
RCTUITextAutocapitalizationTypeFromAutocapitalizationType(newTextInputProps.traits.autocapitalizationType);
}
if (newTextInputProps.traits.autoCorrect != oldTextInputProps.traits.autoCorrect) {
_backedTextInputView.autocorrectionType =
RCTUITextAutocorrectionTypeFromOptionalBool(newTextInputProps.traits.autoCorrect);
}
if (newTextInputProps.traits.contextMenuHidden != oldTextInputProps.traits.contextMenuHidden) {
_backedTextInputView.contextMenuHidden = newTextInputProps.traits.contextMenuHidden;
}
if (newTextInputProps.traits.editable != oldTextInputProps.traits.editable) {
_backedTextInputView.editable = newTextInputProps.traits.editable;
}
if (newTextInputProps.multiline &&
newTextInputProps.traits.dataDetectorTypes != oldTextInputProps.traits.dataDetectorTypes) {
_backedTextInputView.dataDetectorTypes =
RCTUITextViewDataDetectorTypesFromStringVector(newTextInputProps.traits.dataDetectorTypes);
}
if (newTextInputProps.traits.enablesReturnKeyAutomatically !=
oldTextInputProps.traits.enablesReturnKeyAutomatically) {
_backedTextInputView.enablesReturnKeyAutomatically = newTextInputProps.traits.enablesReturnKeyAutomatically;
}
if (newTextInputProps.traits.keyboardAppearance != oldTextInputProps.traits.keyboardAppearance) {
_backedTextInputView.keyboardAppearance =
RCTUIKeyboardAppearanceFromKeyboardAppearance(newTextInputProps.traits.keyboardAppearance);
}
if (newTextInputProps.traits.spellCheck != oldTextInputProps.traits.spellCheck) {
_backedTextInputView.spellCheckingType =
RCTUITextSpellCheckingTypeFromOptionalBool(newTextInputProps.traits.spellCheck);
}
if (newTextInputProps.traits.caretHidden != oldTextInputProps.traits.caretHidden) {
_backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden;
}
if (newTextInputProps.traits.clearButtonMode != oldTextInputProps.traits.clearButtonMode) {
_backedTextInputView.clearButtonMode =
RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(newTextInputProps.traits.clearButtonMode);
}
if (newTextInputProps.traits.scrollEnabled != oldTextInputProps.traits.scrollEnabled) {
_backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled;
}
if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) {
_backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry;
}
if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) {
_backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType);
}
if (newTextInputProps.traits.returnKeyType != oldTextInputProps.traits.returnKeyType) {
_backedTextInputView.returnKeyType = RCTUIReturnKeyTypeFromReturnKeyType(newTextInputProps.traits.returnKeyType);
}
if (newTextInputProps.traits.textContentType != oldTextInputProps.traits.textContentType) {
_backedTextInputView.textContentType = RCTUITextContentTypeFromString(newTextInputProps.traits.textContentType);
}
if (newTextInputProps.traits.passwordRules != oldTextInputProps.traits.passwordRules) {
_backedTextInputView.passwordRules = RCTUITextInputPasswordRulesFromString(newTextInputProps.traits.passwordRules);
}
if (newTextInputProps.traits.smartInsertDelete != oldTextInputProps.traits.smartInsertDelete) {
_backedTextInputView.smartInsertDeleteType =
RCTUITextSmartInsertDeleteTypeFromOptionalBool(newTextInputProps.traits.smartInsertDelete);
}
if (newTextInputProps.traits.showSoftInputOnFocus != oldTextInputProps.traits.showSoftInputOnFocus) {
[self _setShowSoftInputOnFocus:newTextInputProps.traits.showSoftInputOnFocus];
}
// Traits `blurOnSubmit`, `clearTextOnFocus`, and `selectTextOnFocus` were omitted intentionally here
// because they are being checked on-demand.
// Other props:
if (newTextInputProps.placeholder != oldTextInputProps.placeholder) {
_backedTextInputView.placeholder = RCTNSStringFromString(newTextInputProps.placeholder);
}
if (newTextInputProps.placeholderTextColor != oldTextInputProps.placeholderTextColor) {
_backedTextInputView.placeholderColor = RCTUIColorFromSharedColor(newTextInputProps.placeholderTextColor);
}
if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
defaultAttributes[RCTAttributedStringEventEmitterKey] =
_backedTextInputView.defaultTextAttributes[RCTAttributedStringEventEmitterKey];
_backedTextInputView.defaultTextAttributes = defaultAttributes;
}
if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) {
_backedTextInputView.tintColor = RCTUIColorFromSharedColor(newTextInputProps.selectionColor);
}
if (newTextInputProps.inputAccessoryViewID != oldTextInputProps.inputAccessoryViewID) {
_backedTextInputView.inputAccessoryViewID = RCTNSStringFromString(newTextInputProps.inputAccessoryViewID);
}
if (newTextInputProps.inputAccessoryViewButtonLabel != oldTextInputProps.inputAccessoryViewButtonLabel) {
_backedTextInputView.inputAccessoryViewButtonLabel =
RCTNSStringFromString(newTextInputProps.inputAccessoryViewButtonLabel);
}
if (newTextInputProps.disableKeyboardShortcuts != oldTextInputProps.disableKeyboardShortcuts) {
_backedTextInputView.disableKeyboardShortcuts = newTextInputProps.disableKeyboardShortcuts;
}
if (newTextInputProps.acceptDragAndDropTypes != oldTextInputProps.acceptDragAndDropTypes) {
if (!newTextInputProps.acceptDragAndDropTypes.has_value()) {
_backedTextInputView.acceptDragAndDropTypes = nil;
} else {
auto &vector = newTextInputProps.acceptDragAndDropTypes.value();
NSMutableArray<NSString *> *array = [NSMutableArray arrayWithCapacity:vector.size()];
for (const std::string &str : vector) {
[array addObject:[NSString stringWithUTF8String:str.c_str()]];
}
_backedTextInputView.acceptDragAndDropTypes = array;
}
}
[super updateProps:props oldProps:oldProps];
[self setDefaultInputAccessoryView];
}
- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
{
_state = std::static_pointer_cast<const TextInputShadowNode::ConcreteState>(state);
if (!_state) {
assert(false && "State is `null` for <TextInput> component.");
_backedTextInputView.attributedText = nil;
return;
}
auto data = _state->getData();
if (!oldState) {
_mostRecentEventCount = _state->getData().mostRecentEventCount;
}
if (_mostRecentEventCount == _state->getData().mostRecentEventCount) {
_comingFromJS = YES;
[self _setAttributedString:RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox)];
_comingFromJS = NO;
}
}
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
_backedTextInputView.frame =
UIEdgeInsetsInsetRect(self.bounds, RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.borderWidth));
_backedTextInputView.textContainerInset =
RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.contentInsets - layoutMetrics.borderWidth);
if (!CGSizeEqualToSize(_previousContentSize, _backedTextInputView.contentSize) && _eventEmitter) {
_previousContentSize = _backedTextInputView.contentSize;
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onContentSizeChange([self _textInputMetrics]);
}
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_state.reset();
_backedTextInputView.attributedText = nil;
_mostRecentEventCount = 0;
_comingFromJS = NO;
_lastStringStateWasUpdatedWith = nil;
_ignoreNextTextInputCall = NO;
_didMoveToWindow = NO;
_backedTextInputView.inputAccessoryViewID = nil;
_backedTextInputView.inputAccessoryView = nil;
_hasInputAccessoryView = false;
[_backedTextInputView resignFirstResponder];
}
#pragma mark - RCTBackedTextInputDelegate
- (BOOL)textInputShouldBeginEditing
{
return YES;
}
- (void)textInputDidBeginEditing
{
if (_eventEmitter) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onFocus([self _textInputMetrics]);
}
}
- (BOOL)textInputShouldEndEditing
{
return YES;
}
- (void)textInputDidEndEditing
{
if (_eventEmitter) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onEndEditing([self _textInputMetrics]);
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onBlur([self _textInputMetrics]);
}
}
- (BOOL)textInputShouldSubmitOnReturn
{
const SubmitBehavior submitBehavior = [self getSubmitBehavior];
const BOOL shouldSubmit = submitBehavior == SubmitBehavior::Submit || submitBehavior == SubmitBehavior::BlurAndSubmit;
// We send `submit` event here, in `textInputShouldSubmitOnReturn`
// (not in `textInputDidReturn)`, because of semantic of the event:
// `onSubmitEditing` is called when "Submit" button
// (the blue key on onscreen keyboard) did pressed
// (no connection to any specific "submitting" process).
if (_eventEmitter && shouldSubmit) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onSubmitEditing([self _textInputMetrics]);
}
return shouldSubmit;
}
- (BOOL)textInputShouldReturn
{
return [self getSubmitBehavior] == SubmitBehavior::BlurAndSubmit;
}
- (void)textInputDidReturn
{
// Does nothing.
}
- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
{
const auto &props = static_cast<const TextInputProps &>(*_props);
if (!_backedTextInputView.textWasPasted) {
if (_eventEmitter) {
const auto &textInputEventEmitter = static_cast<const TextInputEventEmitter &>(*_eventEmitter);
textInputEventEmitter.onKeyPress({
.text = RCTStringFromNSString(text),
.eventCount = static_cast<int>(_mostRecentEventCount),
});
}
}
if (props.maxLength < std::numeric_limits<int>::max()) {
NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length;
if (allowedLength > 0 && text.length > allowedLength) {
// make sure unicode characters that are longer than 16 bits (such as emojis) are not cut off
NSRange cutOffCharacterRange = [text rangeOfComposedCharacterSequenceAtIndex:allowedLength - 1];
if (cutOffCharacterRange.location + cutOffCharacterRange.length > allowedLength) {
// the character at the length limit takes more than 16bits, truncation should end at the character before
allowedLength = cutOffCharacterRange.location;
}
}
if (allowedLength <= 0) {
return nil;
}
return allowedLength > text.length ? text : [text substringToIndex:allowedLength];
}
return text;
}
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return YES;
}
- (void)textInputDidChange
{
if (_comingFromJS) {
return;
}
if (_ignoreNextTextInputCall && [_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
_ignoreNextTextInputCall = NO;
return;
}
[self _updateState];
if (_eventEmitter) {
const auto &textInputEventEmitter = static_cast<const TextInputEventEmitter &>(*_eventEmitter);
textInputEventEmitter.onChange([self _textInputMetrics]);
}
}
- (void)textInputDidChangeSelection
{
if (_comingFromJS) {
return;
}
// T207198334: Setting a new AttributedString (_comingFromJS) will trigger a selection change before the backing
// string is updated, so indicies won't point to what we want yet. Only respond to user selection change, and let
// `_setAttributedString` handle updating typing attributes if content changes.
[self _updateTypingAttributes];
const auto &props = static_cast<const TextInputProps &>(*_props);
if (props.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
[self textInputDidChange];
_ignoreNextTextInputCall = YES;
}
if (_eventEmitter) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onSelectionChange([self _textInputMetrics]);
}
}
#pragma mark - RCTBackedTextInputDelegate (UIScrollViewDelegate)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_eventEmitter) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onScroll([self _textInputMetrics]);
}
}
#pragma mark - Native Commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTTextInputHandleCommand(self, commandName, args);
}
- (void)focus
{
[_backedTextInputView becomeFirstResponder];
const auto &props = static_cast<const TextInputProps &>(*_props);
if (props.traits.clearTextOnFocus) {
_backedTextInputView.attributedText = nil;
[self textInputDidChange];
}
if (props.traits.selectTextOnFocus) {
[_backedTextInputView selectAll:nil];
[self textInputDidChangeSelection];
}
[self scrollCursorIntoView];
}
- (void)blur
{
[_backedTextInputView resignFirstResponder];
}
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
end:(NSInteger)end
{
if (_mostRecentEventCount != eventCount) {
return;
}
_comingFromJS = YES;
if (value && ![value isEqualToString:_backedTextInputView.attributedText.string]) {
NSAttributedString *attributedString =
[[NSAttributedString alloc] initWithString:value attributes:_backedTextInputView.defaultTextAttributes];
[self _setAttributedString:attributedString];
[self _updateState];
}
UITextPosition *startPosition = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:start];
UITextPosition *endPosition = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:end];
if (startPosition && endPosition) {
UITextRange *range = [_backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition];
[_backedTextInputView setSelectedTextRange:range notifyDelegate:NO];
// ensure we scroll to the selected position
NSInteger offsetEnd = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:range.end];
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetEnd, 0)];
}
_comingFromJS = NO;
}
#pragma mark - Default input accessory view
- (NSString *)returnKeyTypeToString:(UIReturnKeyType)returnKeyType
{
switch (returnKeyType) {
case UIReturnKeyGo:
return @"Go";
case UIReturnKeyNext:
return @"Next";
case UIReturnKeySearch:
return @"Search";
case UIReturnKeySend:
return @"Send";
case UIReturnKeyYahoo:
return @"Yahoo";
case UIReturnKeyGoogle:
return @"Google";
case UIReturnKeyRoute:
return @"Route";
case UIReturnKeyJoin:
return @"Join";
case UIReturnKeyEmergencyCall:
return @"Emergency Call";
default:
return @"Done";
}
}
- (void)initializeReturnKeyType
{
returnKeyTypesSet = [NSSet setWithObjects:@(UIReturnKeyDone),
@(UIReturnKeyGo),
@(UIReturnKeyNext),
@(UIReturnKeySearch),
@(UIReturnKeySend),
@(UIReturnKeyYahoo),
@(UIReturnKeyGoogle),
@(UIReturnKeyRoute),
@(UIReturnKeyJoin),
@(UIReturnKeyRoute),
@(UIReturnKeyEmergencyCall),
nil];
}
- (void)setDefaultInputAccessoryView
{
// InputAccessoryView component sets the inputAccessoryView when inputAccessoryViewID exists
if (_backedTextInputView.inputAccessoryViewID) {
if (_backedTextInputView.isFirstResponder) {
[_backedTextInputView reloadInputViews];
}
return;
}
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 || containsInputAccessoryViewButtonLabel);
if (_hasInputAccessoryView == shouldHaveInputAccessoryView) {
return;
}
_hasInputAccessoryView = shouldHaveInputAccessoryView;
if (shouldHaveInputAccessoryView) {
NSString *buttonLabel = inputAccessoryViewButtonLabel != nil ? inputAccessoryViewButtonLabel
: [self returnKeyTypeToString:returnKeyType];
UIToolbar *toolbarView = [UIToolbar new];
[toolbarView sizeToFit];
UIBarButtonItem *flexibleSpace =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:buttonLabel
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleInputAccessoryDoneButton)];
toolbarView.items = @[ flexibleSpace, doneButton ];
_backedTextInputView.inputAccessoryView = toolbarView;
} else {
_backedTextInputView.inputAccessoryView = nil;
}
if (_backedTextInputView.isFirstResponder) {
[_backedTextInputView reloadInputViews];
}
}
- (void)handleInputAccessoryDoneButton
{
// Ignore the value of whether we submitted; just make sure the submit event is called if necessary.
[self textInputShouldSubmitOnReturn];
if ([self textInputShouldReturn]) {
[_backedTextInputView endEditing:YES];
}
}
#pragma mark - Other
- (TextInputEventEmitter::Metrics)_textInputMetrics
{
return {
.text = RCTStringFromNSString(_backedTextInputView.attributedText.string),
.selectionRange = [self _selectionRange],
.eventCount = static_cast<int>(_mostRecentEventCount),
.contentOffset = RCTPointFromCGPoint(_backedTextInputView.contentOffset),
.contentInset = RCTEdgeInsetsFromUIEdgeInsets(_backedTextInputView.contentInset),
.contentSize = RCTSizeFromCGSize(_backedTextInputView.contentSize),
.layoutMeasurement = RCTSizeFromCGSize(_backedTextInputView.bounds.size),
.zoomScale = _backedTextInputView.zoomScale,
};
}
- (void)_updateState
{
if (!_state) {
return;
}
NSAttributedString *attributedString = _backedTextInputView.attributedText;
auto data = _state->getData();
_lastStringStateWasUpdatedWith = attributedString;
data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString);
_mostRecentEventCount += _comingFromJS ? 0 : 1;
data.mostRecentEventCount = _mostRecentEventCount;
_state->updateState(std::move(data));
}
- (AttributedString::Range)_selectionRange
{
UITextRange *selectedTextRange = _backedTextInputView.selectedTextRange;
NSInteger start = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedTextRange.start];
NSInteger end = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedTextRange.end];
return AttributedString::Range{.location = (int)start, .length = (int)(end - start)};
}
- (void)_restoreTextSelection
{
[self _restoreTextSelectionAndIgnoreCaretChange:NO];
}
- (void)_restoreTextSelectionAndIgnoreCaretChange:(BOOL)ignore
{
const auto &selection = static_cast<const TextInputProps &>(*_props).selection;
if (!selection.has_value()) {
return;
}
auto start = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:selection->start];
auto end = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument offset:selection->end];
auto range = [_backedTextInputView textRangeFromPosition:start toPosition:end];
if (ignore && range.empty) {
return;
}
[_backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
}
- (void)_setAttributedString:(NSAttributedString *)attributedString
{
if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) {
return;
}
UITextRange *selectedRange = _backedTextInputView.selectedTextRange;
NSInteger oldTextLength = _backedTextInputView.attributedText.string.length;
_backedTextInputView.attributedText = attributedString;
// Updating the UITextView attributedText, for example changing the lineHeight, the color or adding
// a new paragraph with \n, causes the cursor to move to the end of the Text and scroll.
// This is fixed by restoring the cursor position and scrolling to that position (iOS issue 652653).
// Maintaining a cursor position relative to the end of the old text.
NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedRange.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = attributedString.string.length - offsetFromEnd;
UITextPosition *position = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:newOffset];
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
// A zero-length selection range can cause the caret position to change on iOS,
// and we have already updated the caret position, so we can safely ignore caret changing in this place.
[self _restoreTextSelectionAndIgnoreCaretChange:YES];
[self _updateTypingAttributes];
_lastStringStateWasUpdatedWith = attributedString;
}
// Ensure that newly typed text will inherit any custom attributes. We follow the logic of RN Android, where attributes
// to the left of the cursor are copied into new text, unless we are at the start of the field, in which case we will
// copy the attributes from text to the right. This allows consistency between backed input and new AttributedText
// https://github.com/facebook/react-native/blob/3102a58df38d96f3dacef0530e4dbb399037fcd2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt#L30
- (void)_updateTypingAttributes
{
if (_backedTextInputView.attributedText.length > 0 && _backedTextInputView.selectedTextRange != nil) {
NSUInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:_backedTextInputView.selectedTextRange.start];
NSUInteger samplePoint = offsetStart == 0 ? 0 : offsetStart - 1;
_backedTextInputView.typingAttributes = [_backedTextInputView.attributedText attributesAtIndex:samplePoint
effectiveRange:NULL];
}
}
- (void)scrollCursorIntoView
{
UITextRange *selectedRange = _backedTextInputView.selectedTextRange;
if (selectedRange.empty) {
NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedRange.start];
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
}
}
- (void)_setMultiline:(BOOL)multiline
{
[_backedTextInputView removeFromSuperview];
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = multiline ? [RCTUITextView new] : [RCTUITextField new];
backedTextInputView.frame = _backedTextInputView.frame;
RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView);
_backedTextInputView = backedTextInputView;
[self addSubview:_backedTextInputView];
}
- (void)_setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus
{
if (showSoftInputOnFocus) {
// Resets to default keyboard.
_backedTextInputView.inputView = nil;
// Without the call to reloadInputViews, the keyboard will not change until the textInput field (the first
// responder) loses and regains focus.
if (_backedTextInputView.isFirstResponder) {
[_backedTextInputView reloadInputViews];
}
} else {
// Hides keyboard, but keeps blinking cursor.
_backedTextInputView.inputView = [UIView new];
}
}
- (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText
{
// When the dictation is running we can't update the attributed text on the backed up text view
// because setting the attributed string will kill the dictation. This means that we can't impose
// the settings on a dictation.
// Similarly, when the user is in the middle of inputting some text in Japanese/Chinese, there will be styling on the
// text that we should disregard. See
// https://developer.apple.com/documentation/uikit/uitextinput/1614489-markedtextrange?language=objc for more info.
// Also, updating the attributed text while inputting Korean language will break input mechanism.
// If the user added an emoji, the system adds a font attribute for the emoji and stores the original font in
// NSOriginalFont. Lastly, when entering a password, etc., there will be additional styling on the field as the native
// text view handles showing the last character for a split second.
__block BOOL fontHasBeenUpdatedBySystem = false;
[oldText enumerateAttribute:@"NSOriginalFont"
inRange:NSMakeRange(0, oldText.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value) {
fontHasBeenUpdatedBySystem = true;
}
}];
BOOL shouldFallbackToBareTextComparison =
[_backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] ||
[_backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"ko-KR"] ||
_backedTextInputView.markedTextRange || _backedTextInputView.isSecureTextEntry || fontHasBeenUpdatedBySystem;
if (shouldFallbackToBareTextComparison) {
return [newText.string isEqualToString:oldText.string];
} else {
return RCTIsAttributedStringEffectivelySame(
newText, oldText, _originalTypingAttributes, static_cast<const TextInputProps &>(*_props).textAttributes);
}
}
- (SubmitBehavior)getSubmitBehavior
{
const auto &props = static_cast<const TextInputProps &>(*_props);
return props.getNonDefaultSubmitBehavior();
}
@end
Class<RCTComponentViewProtocol> RCTTextInputCls(void)
{
return RCTTextInputComponentView.class;
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RCTTextInputViewProtocol <NSObject>
- (void)focus;
- (void)blur;
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
end:(NSInteger)end;
@end
RCT_EXTERN inline void
RCTTextInputHandleCommand(id<RCTTextInputViewProtocol> componentView, const NSString *commandName, const NSArray *args)
{
if ([commandName isEqualToString:@"focus"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
return;
}
#endif
[componentView focus];
return;
}
if ([commandName isEqualToString:@"blur"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
return;
}
#endif
[componentView blur];
return;
}
if ([commandName isEqualToString:@"setTextAndSelection"]) {
#if RCT_DEBUG
if ([args count] != 4) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 4);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"number", @"TextInput", commandName, @"1st")) {
return;
}
#endif
NSInteger eventCount = [(NSNumber *)arg0 intValue];
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (![arg1 isKindOfClass:[NSNull class]] &&
!RCTValidateTypeOfViewCommandArgument(arg1, [NSString class], @"string", @"TextInput", commandName, @"2nd")) {
return;
}
#endif
NSString *value = [arg1 isKindOfClass:[NSNull class]] ? nil : (NSString *)arg1;
NSObject *arg2 = args[2];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg2, [NSNumber class], @"number", @"TextInput", commandName, @"3rd")) {
return;
}
#endif
NSInteger start = [(NSNumber *)arg2 intValue];
NSObject *arg3 = args[3];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg3, [NSNumber class], @"number", @"TextInput", commandName, @"4th")) {
return;
}
#endif
NSInteger end = [(NSNumber *)arg3 intValue];
[componentView setTextAndSelection:eventCount value:value start:start end:end];
return;
}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"TextInput", commandName);
#endif
}
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <optional>
#import <React/RCTBackedTextInputViewProtocol.h>
#import <react/renderer/components/iostextinput/primitives.h>
NS_ASSUME_NONNULL_BEGIN
void RCTCopyBackedTextInput(
UIView<RCTBackedTextInputViewProtocol> *fromTextInput,
UIView<RCTBackedTextInputViewProtocol> *toTextInput);
UITextAutocorrectionType RCTUITextAutocorrectionTypeFromOptionalBool(std::optional<bool> autoCorrect);
UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizationType(
facebook::react::AutocapitalizationType autocapitalizationType);
UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(
facebook::react::KeyboardAppearance keyboardAppearance);
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional<bool> spellCheck);
UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(
facebook::react::TextInputAccessoryVisibilityMode mode);
UIKeyboardType RCTUIKeyboardTypeFromKeyboardType(facebook::react::KeyboardType keyboardType);
UIReturnKeyType RCTUIReturnKeyTypeFromReturnKeyType(facebook::react::ReturnKeyType returnKeyType);
UITextContentType RCTUITextContentTypeFromString(const std::string &contentType);
UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(const std::string &passwordRules);
UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional<bool> smartInsertDelete);
UIDataDetectorTypes RCTUITextViewDataDetectorTypesFromStringVector(const std::vector<std::string> &dataDetectorTypes);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,300 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTextInputUtils.h"
#import <React/RCTConversions.h>
using namespace facebook::react;
static NSAttributedString *RCTSanitizeAttributedString(NSAttributedString *attributedString)
{
// Here we need to remove text attributes specific to particular kind of TextInput on iOS (e.g. limiting line number).
// TODO: Implement it properly.
return [[NSAttributedString alloc] initWithString:attributedString.string];
}
void RCTCopyBackedTextInput(
UIView<RCTBackedTextInputViewProtocol> *fromTextInput,
UIView<RCTBackedTextInputViewProtocol> *toTextInput)
{
toTextInput.attributedText = RCTSanitizeAttributedString(fromTextInput.attributedText);
toTextInput.placeholder = fromTextInput.placeholder;
toTextInput.placeholderColor = fromTextInput.placeholderColor;
toTextInput.textContainerInset = fromTextInput.textContainerInset;
toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView;
toTextInput.textInputDelegate = fromTextInput.textInputDelegate;
toTextInput.placeholderColor = fromTextInput.placeholderColor;
toTextInput.defaultTextAttributes = fromTextInput.defaultTextAttributes;
toTextInput.autocapitalizationType = fromTextInput.autocapitalizationType;
toTextInput.autocorrectionType = fromTextInput.autocorrectionType;
toTextInput.contextMenuHidden = fromTextInput.contextMenuHidden;
toTextInput.editable = fromTextInput.editable;
toTextInput.enablesReturnKeyAutomatically = fromTextInput.enablesReturnKeyAutomatically;
toTextInput.keyboardAppearance = fromTextInput.keyboardAppearance;
toTextInput.spellCheckingType = fromTextInput.spellCheckingType;
toTextInput.caretHidden = fromTextInput.caretHidden;
toTextInput.clearButtonMode = fromTextInput.clearButtonMode;
toTextInput.scrollEnabled = fromTextInput.scrollEnabled;
toTextInput.secureTextEntry = fromTextInput.secureTextEntry;
toTextInput.keyboardType = fromTextInput.keyboardType;
toTextInput.textContentType = fromTextInput.textContentType;
toTextInput.smartInsertDeleteType = fromTextInput.smartInsertDeleteType;
toTextInput.passwordRules = fromTextInput.passwordRules;
toTextInput.disableKeyboardShortcuts = fromTextInput.disableKeyboardShortcuts;
toTextInput.acceptDragAndDropTypes = fromTextInput.acceptDragAndDropTypes;
[toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO];
}
UITextAutocorrectionType RCTUITextAutocorrectionTypeFromOptionalBool(std::optional<bool> autoCorrect)
{
return autoCorrect.has_value() ? (*autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo)
: UITextAutocorrectionTypeDefault;
}
UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizationType(
AutocapitalizationType autocapitalizationType)
{
switch (autocapitalizationType) {
case AutocapitalizationType::None:
return UITextAutocapitalizationTypeNone;
case AutocapitalizationType::Words:
return UITextAutocapitalizationTypeWords;
case AutocapitalizationType::Sentences:
return UITextAutocapitalizationTypeSentences;
case AutocapitalizationType::Characters:
return UITextAutocapitalizationTypeAllCharacters;
}
}
UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppearance keyboardAppearance)
{
switch (keyboardAppearance) {
case KeyboardAppearance::Default:
return UIKeyboardAppearanceDefault;
case KeyboardAppearance::Light:
return UIKeyboardAppearanceLight;
case KeyboardAppearance::Dark:
return UIKeyboardAppearanceDark;
}
}
UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional<bool> spellCheck)
{
return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo)
: UITextSpellCheckingTypeDefault;
}
UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(
facebook::react::TextInputAccessoryVisibilityMode mode)
{
switch (mode) {
case TextInputAccessoryVisibilityMode::Never:
return UITextFieldViewModeNever;
case TextInputAccessoryVisibilityMode::WhileEditing:
return UITextFieldViewModeWhileEditing;
case TextInputAccessoryVisibilityMode::UnlessEditing:
return UITextFieldViewModeUnlessEditing;
case TextInputAccessoryVisibilityMode::Always:
return UITextFieldViewModeAlways;
}
}
UIKeyboardType RCTUIKeyboardTypeFromKeyboardType(KeyboardType keyboardType)
{
switch (keyboardType) {
// Universal
case KeyboardType::Default:
return UIKeyboardTypeDefault;
case KeyboardType::EmailAddress:
return UIKeyboardTypeEmailAddress;
case KeyboardType::Numeric:
return UIKeyboardTypeDecimalPad;
case KeyboardType::PhonePad:
return UIKeyboardTypePhonePad;
case KeyboardType::NumberPad:
return UIKeyboardTypeNumberPad;
case KeyboardType::DecimalPad:
return UIKeyboardTypeDecimalPad;
// iOS-only
case KeyboardType::ASCIICapable:
return UIKeyboardTypeASCIICapable;
case KeyboardType::NumbersAndPunctuation:
return UIKeyboardTypeNumbersAndPunctuation;
case KeyboardType::URL:
return UIKeyboardTypeURL;
case KeyboardType::NamePhonePad:
return UIKeyboardTypeNamePhonePad;
case KeyboardType::Twitter:
return UIKeyboardTypeTwitter;
case KeyboardType::WebSearch:
return UIKeyboardTypeWebSearch;
case KeyboardType::ASCIICapableNumberPad:
return UIKeyboardTypeASCIICapableNumberPad;
// Android-only
case KeyboardType::VisiblePassword:
return UIKeyboardTypeDefault;
}
}
UIReturnKeyType RCTUIReturnKeyTypeFromReturnKeyType(ReturnKeyType returnKeyType)
{
switch (returnKeyType) {
case ReturnKeyType::Default:
return UIReturnKeyDefault;
case ReturnKeyType::Done:
return UIReturnKeyDone;
case ReturnKeyType::Go:
return UIReturnKeyGo;
case ReturnKeyType::Next:
return UIReturnKeyNext;
case ReturnKeyType::Search:
return UIReturnKeySearch;
case ReturnKeyType::Send:
return UIReturnKeySend;
// iOS-only
case ReturnKeyType::EmergencyCall:
return UIReturnKeyEmergencyCall;
case ReturnKeyType::Google:
return UIReturnKeyGoogle;
case ReturnKeyType::Join:
return UIReturnKeyJoin;
case ReturnKeyType::Route:
return UIReturnKeyRoute;
case ReturnKeyType::Yahoo:
return UIReturnKeyYahoo;
case ReturnKeyType::Continue:
return UIReturnKeyContinue;
// Android-only
case ReturnKeyType::None:
case ReturnKeyType::Previous:
return UIReturnKeyDefault;
}
}
UITextContentType RCTUITextContentTypeFromString(const std::string &contentType)
{
static dispatch_once_t onceToken;
static NSDictionary<NSString *, NSString *> *contentTypeMap;
dispatch_once(&onceToken, ^{
NSMutableDictionary<NSString *, NSString *> *mutableContentTypeMap = [NSMutableDictionary new];
[mutableContentTypeMap addEntriesFromDictionary:@{
@"" : @"",
@"none" : @"",
@"URL" : UITextContentTypeURL,
@"addressCity" : UITextContentTypeAddressCity,
@"addressCityAndState" : UITextContentTypeAddressCityAndState,
@"addressState" : UITextContentTypeAddressState,
@"countryName" : UITextContentTypeCountryName,
@"creditCardNumber" : UITextContentTypeCreditCardNumber,
@"emailAddress" : UITextContentTypeEmailAddress,
@"familyName" : UITextContentTypeFamilyName,
@"fullStreetAddress" : UITextContentTypeFullStreetAddress,
@"givenName" : UITextContentTypeGivenName,
@"jobTitle" : UITextContentTypeJobTitle,
@"location" : UITextContentTypeLocation,
@"middleName" : UITextContentTypeMiddleName,
@"name" : UITextContentTypeName,
@"namePrefix" : UITextContentTypeNamePrefix,
@"nameSuffix" : UITextContentTypeNameSuffix,
@"nickname" : UITextContentTypeNickname,
@"organizationName" : UITextContentTypeOrganizationName,
@"postalCode" : UITextContentTypePostalCode,
@"streetAddressLine1" : UITextContentTypeStreetAddressLine1,
@"streetAddressLine2" : UITextContentTypeStreetAddressLine2,
@"sublocality" : UITextContentTypeSublocality,
@"telephoneNumber" : UITextContentTypeTelephoneNumber,
@"username" : UITextContentTypeUsername,
@"password" : UITextContentTypePassword,
@"newPassword" : UITextContentTypeNewPassword,
@"oneTimeCode" : UITextContentTypeOneTimeCode,
}];
if (@available(iOS 15.0, *)) {
[mutableContentTypeMap addEntriesFromDictionary:@{
@"dateTime" : UITextContentTypeDateTime,
@"flightNumber" : UITextContentTypeFlightNumber,
@"shipmentTrackingNumber" : UITextContentTypeShipmentTrackingNumber,
}];
}
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */
if (@available(iOS 17.0, *)) {
[mutableContentTypeMap addEntriesFromDictionary:@{
@"creditCardExpiration" : UITextContentTypeCreditCardExpiration,
@"creditCardExpirationMonth" : UITextContentTypeCreditCardExpirationMonth,
@"creditCardExpirationYear" : UITextContentTypeCreditCardExpirationYear,
@"creditCardSecurityCode" : UITextContentTypeCreditCardSecurityCode,
@"creditCardType" : UITextContentTypeCreditCardType,
@"creditCardName" : UITextContentTypeCreditCardName,
@"creditCardGivenName" : UITextContentTypeCreditCardGivenName,
@"creditCardMiddleName" : UITextContentTypeCreditCardMiddleName,
@"creditCardFamilyName" : UITextContentTypeCreditCardFamilyName,
@"birthdate" : UITextContentTypeBirthdate,
@"birthdateDay" : UITextContentTypeBirthdateDay,
@"birthdateMonth" : UITextContentTypeBirthdateMonth,
@"birthdateYear" : UITextContentTypeBirthdateYear,
}];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400 /* __IPHONE_17_4 */
if (@available(iOS 17.4, *)) {
[mutableContentTypeMap addEntriesFromDictionary:@{
@"cellularEID" : UITextContentTypeCellularEID,
@"cellularIMEI" : UITextContentTypeCellularIMEI,
}];
}
#endif
#endif
contentTypeMap = mutableContentTypeMap;
});
return contentTypeMap[RCTNSStringFromString(contentType)] ?: @"";
}
UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(const std::string &passwordRules)
{
return [UITextInputPasswordRules passwordRulesWithDescriptor:RCTNSStringFromStringNilIfEmpty(passwordRules)];
}
UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional<bool> smartInsertDelete)
{
return smartInsertDelete.has_value()
? (*smartInsertDelete ? UITextSmartInsertDeleteTypeYes : UITextSmartInsertDeleteTypeNo)
: UITextSmartInsertDeleteTypeDefault;
}
UIDataDetectorTypes RCTUITextViewDataDetectorTypesFromStringVector(const std::vector<std::string> &dataDetectorTypes)
{
static dispatch_once_t onceToken;
static NSDictionary<NSString *, NSNumber *> *dataDetectorTypesMap = nil;
dispatch_once(&onceToken, ^{
dataDetectorTypesMap = @{
@"link" : @(UIDataDetectorTypeLink),
@"phoneNumber" : @(UIDataDetectorTypePhoneNumber),
@"address" : @(UIDataDetectorTypeAddress),
@"calendarEvent" : @(UIDataDetectorTypeCalendarEvent),
@"trackingNumber" : @(UIDataDetectorTypeShipmentTrackingNumber),
@"flightNumber" : @(UIDataDetectorTypeFlightNumber),
@"lookupSuggestion" : @(UIDataDetectorTypeLookupSuggestion),
@"all" : @(UIDataDetectorTypeAll)
};
});
UIDataDetectorTypes ret = UIDataDetectorTypeNone;
for (const auto &dataType : dataDetectorTypes) {
NSNumber *val = dataDetectorTypesMap[RCTNSStringFromString(dataType)];
if (val) {
ret |= (UIDataDetectorTypes)val.unsignedIntValue;
}
}
return ret;
}