yeet
This commit is contained in:
16
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextInputView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTMultilineTextInputView : RCTBaseTextInputView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
76
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
generated
vendored
Normal file
76
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTMultilineTextInputView.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import <React/RCTUITextView.h>
|
||||
|
||||
@implementation RCTMultilineTextInputView
|
||||
{
|
||||
RCTUITextView *_backedTextInputView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
// `blurOnSubmit` defaults to `false` for <TextInput multiline={true}> by design.
|
||||
self.blurOnSubmit = NO;
|
||||
|
||||
_backedTextInputView = [[RCTUITextView alloc] initWithFrame:self.bounds];
|
||||
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInputView.textInputDelegate = self;
|
||||
|
||||
[self addSubview:_backedTextInputView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
return _backedTextInputView;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
RCTDirectEventBlock onScroll = self.onScroll;
|
||||
|
||||
if (onScroll) {
|
||||
CGPoint contentOffset = scrollView.contentOffset;
|
||||
CGSize contentSize = scrollView.contentSize;
|
||||
CGSize size = scrollView.bounds.size;
|
||||
UIEdgeInsets contentInset = scrollView.contentInset;
|
||||
|
||||
onScroll(@{
|
||||
@"contentOffset": @{
|
||||
@"x": @(contentOffset.x),
|
||||
@"y": @(contentOffset.y)
|
||||
},
|
||||
@"contentInset": @{
|
||||
@"top": @(contentInset.top),
|
||||
@"left": @(contentInset.left),
|
||||
@"bottom": @(contentInset.bottom),
|
||||
@"right": @(contentInset.right)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(contentSize.width),
|
||||
@"height": @(contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(size.width),
|
||||
@"height": @(size.height)
|
||||
},
|
||||
@"zoomScale": @(scrollView.zoomScale ?: 1),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.h
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextInputViewManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTMultilineTextInputViewManager : RCTBaseTextInputViewManager
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
26
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m
generated
vendored
Normal file
26
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTMultilineTextInputViewManager.h>
|
||||
#import <React/RCTMultilineTextInputView.h>
|
||||
|
||||
@implementation RCTMultilineTextInputViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
#pragma mark - Multiline <TextInput> (aka TextView) specific properties
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes)
|
||||
#endif
|
||||
|
||||
@end
|
42
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h
generated
vendored
Normal file
42
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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/RCTBackedTextInputViewProtocol.h>
|
||||
#import <React/RCTBackedTextInputDelegate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
* Just regular UITextView... but much better!
|
||||
*/
|
||||
@interface RCTUITextView : UITextView <RCTBackedTextInputViewProtocol>
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
|
||||
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL contextMenuHidden;
|
||||
@property (nonatomic, assign, readonly) BOOL textWasPasted;
|
||||
@property (nonatomic, copy, nullable) NSString *placeholder;
|
||||
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
|
||||
|
||||
@property (nonatomic, assign) CGFloat preferredMaxLayoutWidth;
|
||||
|
||||
// The `clearButtonMode` property actually is not supported yet;
|
||||
// it's declared here only to conform to the interface.
|
||||
@property (nonatomic, assign) UITextFieldViewMode clearButtonMode;
|
||||
|
||||
// The `caretHidden` property actually is not supported yet;
|
||||
// it's declared here only to conform to the interface.
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
309
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m
generated
vendored
Normal file
309
node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m
generated
vendored
Normal file
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTUITextView.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
||||
#import <React/RCTTextAttributes.h>
|
||||
|
||||
@implementation RCTUITextView
|
||||
{
|
||||
UILabel *_placeholderView;
|
||||
UITextView *_detachedTextView;
|
||||
RCTBackedTextViewDelegateAdapter *_textInputDelegateAdapter;
|
||||
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
|
||||
}
|
||||
|
||||
static UIFont *defaultPlaceholderFont()
|
||||
{
|
||||
return [UIFont systemFontOfSize:17];
|
||||
}
|
||||
|
||||
static UIColor *defaultPlaceholderColor()
|
||||
{
|
||||
// Default placeholder color from UITextField.
|
||||
return [UIColor colorWithRed:0 green:0 blue:0.0980392 alpha:0.22];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(textDidChange)
|
||||
name:UITextViewTextDidChangeNotification
|
||||
object:self];
|
||||
|
||||
_placeholderView = [[UILabel alloc] initWithFrame:self.bounds];
|
||||
_placeholderView.isAccessibilityElement = NO;
|
||||
_placeholderView.numberOfLines = 0;
|
||||
[self addSubview:_placeholderView];
|
||||
|
||||
_textInputDelegateAdapter = [[RCTBackedTextViewDelegateAdapter alloc] initWithTextView:self];
|
||||
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.textColor = [UIColor blackColor];
|
||||
// This line actually removes 5pt (default value) left and right padding in UITextView.
|
||||
self.textContainer.lineFragmentPadding = 0;
|
||||
#if !TARGET_OS_TV
|
||||
self.scrollsToTop = NO;
|
||||
#endif
|
||||
self.scrollEnabled = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
|
||||
{
|
||||
// UITextView is accessible by default (some nested views are) and disabling that is not supported.
|
||||
// On iOS accessible elements cannot be nested, therefore enabling accessibility for some container view
|
||||
// (even in a case where this view is a part of public API of TextInput on iOS) shadows some features implemented inside the component.
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
{
|
||||
NSMutableString *accessibilityLabel = [NSMutableString new];
|
||||
|
||||
NSString *superAccessibilityLabel = [super accessibilityLabel];
|
||||
if (superAccessibilityLabel.length > 0) {
|
||||
[accessibilityLabel appendString:superAccessibilityLabel];
|
||||
}
|
||||
|
||||
if (self.placeholder.length > 0 && self.attributedText.string.length == 0) {
|
||||
if (accessibilityLabel.length > 0) {
|
||||
[accessibilityLabel appendString:@" "];
|
||||
}
|
||||
[accessibilityLabel appendString:self.placeholder];
|
||||
}
|
||||
|
||||
return accessibilityLabel;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
_placeholder = placeholder;
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderColor:(UIColor *)placeholderColor
|
||||
{
|
||||
_placeholderColor = placeholderColor;
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setDefaultTextAttributes:(NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
||||
{
|
||||
if ([_defaultTextAttributes isEqualToDictionary:defaultTextAttributes]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultTextAttributes = defaultTextAttributes;
|
||||
self.typingAttributes = defaultTextAttributes;
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
||||
{
|
||||
return _defaultTextAttributes;
|
||||
}
|
||||
|
||||
- (void)textDidChange
|
||||
{
|
||||
_textWasPasted = NO;
|
||||
[self _invalidatePlaceholderVisibility];
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (void)setFont:(UIFont *)font
|
||||
{
|
||||
[super setFont:font];
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setTextAlignment:(NSTextAlignment)textAlignment
|
||||
{
|
||||
[super setTextAlignment:textAlignment];
|
||||
_placeholderView.textAlignment = textAlignment;
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText
|
||||
{
|
||||
// Using `setAttributedString:` while user is typing breaks some internal mechanics
|
||||
// when entering complex input languages such as Chinese, Korean or Japanese.
|
||||
// see: https://github.com/facebook/react-native/issues/19339
|
||||
|
||||
// We try to avoid calling this method as much as we can.
|
||||
// If the text has changed, there is nothing we can do.
|
||||
if (![super.attributedText.string isEqualToString:attributedText.string]) {
|
||||
[super setAttributedText:attributedText];
|
||||
} else {
|
||||
// But if the text is preserved, we just copying the attributes from the source string.
|
||||
if (![super.attributedText isEqualToAttributedString:attributedText]) {
|
||||
[self copyTextAttributesFrom:attributedText];
|
||||
}
|
||||
}
|
||||
|
||||
[self textDidChange];
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
|
||||
{
|
||||
if (!notifyDelegate) {
|
||||
// We have to notify an adapter that following selection change was initiated programmatically,
|
||||
// so the adapter must not generate a notification for it.
|
||||
[_textInputDelegateAdapter skipNextTextInputDidChangeSelectionEventWithTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
[super setSelectedTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender
|
||||
{
|
||||
[super paste:sender];
|
||||
_textWasPasted = YES;
|
||||
}
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset animated:(__unused BOOL)animated
|
||||
{
|
||||
// Turning off scroll animation.
|
||||
// This fixes the problem also known as "flaky scrolling".
|
||||
[super setContentOffset:contentOffset animated:NO];
|
||||
}
|
||||
|
||||
- (void)selectAll:(id)sender
|
||||
{
|
||||
[super selectAll:sender];
|
||||
|
||||
// `selectAll:` does not work for UITextView when it's being called inside UITextView's delegate methods.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UITextRange *selectionRange = [self textRangeFromPosition:self.beginningOfDocument toPosition:self.endOfDocument];
|
||||
[self setSelectedTextRange:selectionRange notifyDelegate:NO];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (CGFloat)preferredMaxLayoutWidth
|
||||
{
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return _preferredMaxLayoutWidth ?: self.placeholderSize.width;
|
||||
}
|
||||
|
||||
- (CGSize)placeholderSize
|
||||
{
|
||||
UIEdgeInsets textContainerInset = self.textContainerInset;
|
||||
NSString *placeholder = self.placeholder ?: @"";
|
||||
CGSize maxPlaceholderSize = CGSizeMake(UIEdgeInsetsInsetRect(self.bounds, textContainerInset).size.width, CGFLOAT_MAX);
|
||||
CGSize placeholderSize = [placeholder boundingRectWithSize:maxPlaceholderSize options:NSStringDrawingUsesLineFragmentOrigin attributes:[self _placeholderTextAttributes] context:nil].size;
|
||||
placeholderSize = CGSizeMake(RCTCeilPixelValue(placeholderSize.width), RCTCeilPixelValue(placeholderSize.height));
|
||||
placeholderSize.width += textContainerInset.left + textContainerInset.right;
|
||||
placeholderSize.height += textContainerInset.top + textContainerInset.bottom;
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`; as `sizeThatFits:` does).
|
||||
return placeholderSize;
|
||||
}
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
CGSize contentSize = super.contentSize;
|
||||
CGSize placeholderSize = _placeholderView.isHidden ? CGSizeZero : self.placeholderSize;
|
||||
// When a text input is empty, it actually displays a placehoder.
|
||||
// So, we have to consider `placeholderSize` as a minimum `contentSize`.
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return CGSizeMake(
|
||||
MAX(contentSize.width, placeholderSize.width),
|
||||
MAX(contentSize.height, placeholderSize.height));
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, self.textContainerInset);
|
||||
CGFloat placeholderHeight = [_placeholderView sizeThatFits:textFrame.size].height;
|
||||
textFrame.size.height = MIN(placeholderHeight, textFrame.size.height);
|
||||
_placeholderView.frame = textFrame;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return [self sizeThatFits:CGSizeMake(self.preferredMaxLayoutWidth, CGFLOAT_MAX)];
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
// Returned fitting size depends on text size and placeholder size.
|
||||
CGSize textSize = [super sizeThatFits:size];
|
||||
CGSize placeholderSize = self.placeholderSize;
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return CGSizeMake(MAX(textSize.width, placeholderSize.width), MAX(textSize.height, placeholderSize.height));
|
||||
}
|
||||
|
||||
#pragma mark - Context Menu
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
if (_contextMenuHidden) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
#pragma mark - Placeholder
|
||||
|
||||
- (void)_invalidatePlaceholderVisibility
|
||||
{
|
||||
BOOL isVisible = _placeholder.length != 0 && self.attributedText.length == 0;
|
||||
_placeholderView.hidden = !isVisible;
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholder
|
||||
{
|
||||
_placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder ?: @"" attributes:[self _placeholderTextAttributes]];
|
||||
}
|
||||
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)_placeholderTextAttributes
|
||||
{
|
||||
NSMutableDictionary<NSAttributedStringKey, id> *textAttributes = [_defaultTextAttributes mutableCopy] ?: [NSMutableDictionary new];
|
||||
|
||||
[textAttributes setValue:self.placeholderColor ?: defaultPlaceholderColor() forKey:NSForegroundColorAttributeName];
|
||||
|
||||
if (![textAttributes objectForKey:NSFontAttributeName]) {
|
||||
[textAttributes setValue:defaultPlaceholderFont() forKey:NSFontAttributeName];
|
||||
}
|
||||
|
||||
return textAttributes;
|
||||
}
|
||||
|
||||
#pragma mark - Utility Methods
|
||||
|
||||
- (void)copyTextAttributesFrom:(NSAttributedString *)sourceString
|
||||
{
|
||||
[self.textStorage beginEditing];
|
||||
|
||||
NSTextStorage *textStorage = self.textStorage;
|
||||
[sourceString enumerateAttributesInRange:NSMakeRange(0, sourceString.length)
|
||||
options:NSAttributedStringEnumerationReverse
|
||||
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
|
||||
[textStorage setAttributes:attrs range:range];
|
||||
}];
|
||||
|
||||
[self.textStorage endEditing];
|
||||
}
|
||||
|
||||
@end
|
42
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
generated
vendored
Normal file
42
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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>
|
||||
|
||||
@protocol RCTBackedTextInputViewProtocol;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol RCTBackedTextInputDelegate <NSObject>
|
||||
|
||||
- (BOOL)textInputShouldBeginEditing; // Return `NO` to disallow editing.
|
||||
- (void)textInputDidBeginEditing;
|
||||
|
||||
- (BOOL)textInputShouldEndEditing; // Return `YES` to allow editing to stop and to resign first responder status. `NO` to disallow the editing session to end.
|
||||
- (void)textInputDidEndEditing; // May be called if forced even if `textInputShouldEndEditing` returns `NO` (e.g. view removed from window) or `[textInput endEditing:YES]` called.
|
||||
|
||||
- (BOOL)textInputShouldReturn; // May be called right before `textInputShouldEndEditing` if "Return" button was pressed.
|
||||
- (void)textInputDidReturn;
|
||||
|
||||
/*
|
||||
* Called before any change in the TextInput. The delegate has the opportunity to change the replacement string or reject the change completely.
|
||||
* To change the replacement, return the changed version of the `text`.
|
||||
* To accept the change, return `text` argument as-is.
|
||||
* To reject the change, return `nil`.
|
||||
*/
|
||||
- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range;
|
||||
- (void)textInputDidChange;
|
||||
|
||||
- (void)textInputDidChangeSelection;
|
||||
|
||||
@optional
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
36
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
generated
vendored
Normal file
36
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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 "RCTBackedTextInputViewProtocol.h"
|
||||
#import "RCTBackedTextInputDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
|
||||
|
||||
@interface RCTBackedTextFieldDelegateAdapter : NSObject
|
||||
|
||||
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInputView;
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
||||
- (void)selectedTextRangeWasSet;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
|
||||
|
||||
@interface RCTBackedTextViewDelegateAdapter : NSObject
|
||||
|
||||
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
286
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m
generated
vendored
Normal file
286
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
||||
|
||||
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
|
||||
|
||||
static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext;
|
||||
|
||||
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTBackedTextFieldDelegateAdapter {
|
||||
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInputView;
|
||||
BOOL _textDidChangeIsComing;
|
||||
UITextRange *_previousSelectedTextRange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_backedTextInputView = backedTextInputView;
|
||||
backedTextInputView.delegate = self;
|
||||
|
||||
[_backedTextInputView addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
|
||||
[_backedTextInputView addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
|
||||
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldBeginEditing:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
|
||||
}
|
||||
|
||||
- (void)textFieldDidBeginEditing:(__unused UITextField *)textField
|
||||
{
|
||||
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(__unused UITextField *)textField
|
||||
{
|
||||
if (_textDidChangeIsComing) {
|
||||
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
||||
// which was triggered by losing focus. So, we call it manually.
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||
{
|
||||
NSString *newText =
|
||||
[_backedTextInputView.textInputDelegate textInputShouldChangeText:string inRange:range];
|
||||
|
||||
if (newText == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([newText isEqualToString:string]) {
|
||||
_textDidChangeIsComing = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
|
||||
[attributedString replaceCharactersInRange:range withString:newText];
|
||||
[_backedTextInputView setAttributedText:[attributedString copy]];
|
||||
|
||||
// Setting selection to the end of the replaced text.
|
||||
UITextPosition *position =
|
||||
[_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
|
||||
offset:(range.location + newText.length)];
|
||||
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
|
||||
[self textFieldDidChange];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(__unused UITextField *)textField
|
||||
{
|
||||
return [_backedTextInputView.textInputDelegate textInputShouldReturn];
|
||||
}
|
||||
|
||||
#pragma mark - UIControlEventEditing* Family Events
|
||||
|
||||
- (void)textFieldDidChange
|
||||
{
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChange];
|
||||
|
||||
// `selectedTextRangeWasSet` isn't triggered during typing.
|
||||
[self textFieldProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditingOnExit
|
||||
{
|
||||
[_backedTextInputView.textInputDelegate textInputDidReturn];
|
||||
}
|
||||
|
||||
#pragma mark - UIKeyboardInput (private UIKit protocol)
|
||||
|
||||
// This method allows us to detect a [Backspace] `keyPress`
|
||||
// even when there is no more text in the `UITextField`.
|
||||
- (BOOL)keyboardInputShouldDelete:(__unused UITextField *)textField
|
||||
{
|
||||
[_backedTextInputView.textInputDelegate textInputShouldChangeText:@"" inRange:NSMakeRange(0, 0)];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Public Interface
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
||||
{
|
||||
_previousSelectedTextRange = textRange;
|
||||
}
|
||||
|
||||
- (void)selectedTextRangeWasSet
|
||||
{
|
||||
[self textFieldProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
#pragma mark - Generalization
|
||||
|
||||
- (void)textFieldProbablyDidChangeSelection
|
||||
{
|
||||
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
|
||||
|
||||
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTBackedTextViewDelegateAdapter {
|
||||
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
|
||||
BOOL _textDidChangeIsComing;
|
||||
UITextRange *_previousSelectedTextRange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_backedTextInputView = backedTextInputView;
|
||||
backedTextInputView.delegate = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(__unused UITextView *)textView
|
||||
{
|
||||
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(__unused UITextView *)textView
|
||||
{
|
||||
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textViewShouldEndEditing:(__unused UITextView *)textView
|
||||
{
|
||||
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(__unused UITextView *)textView
|
||||
{
|
||||
if (_textDidChangeIsComing) {
|
||||
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
||||
// which was triggered by losing focus. So, we call it manually.
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
{
|
||||
// Custom implementation of `textInputShouldReturn` and `textInputDidReturn` pair for `UITextView`.
|
||||
if (!_backedTextInputView.textWasPasted && [text isEqualToString:@"\n"]) {
|
||||
if ([_backedTextInputView.textInputDelegate textInputShouldReturn]) {
|
||||
[_backedTextInputView.textInputDelegate textInputDidReturn];
|
||||
[_backedTextInputView endEditing:NO];
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *newText =
|
||||
[_backedTextInputView.textInputDelegate textInputShouldChangeText:text inRange:range];
|
||||
|
||||
if (newText == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([newText isEqualToString:text]) {
|
||||
_textDidChangeIsComing = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
|
||||
[attributedString replaceCharactersInRange:range withString:newText];
|
||||
[_backedTextInputView setAttributedText:[attributedString copy]];
|
||||
|
||||
// Setting selection to the end of the replaced text.
|
||||
UITextPosition *position =
|
||||
[_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
|
||||
offset:(range.location + newText.length)];
|
||||
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
|
||||
[self textViewDidChange:_backedTextInputView];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(__unused UITextView *)textView
|
||||
{
|
||||
_textDidChangeIsComing = NO;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChange];
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
|
||||
{
|
||||
[self textViewProbablyDidChangeSelection];
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
|
||||
[_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Public Interface
|
||||
|
||||
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
||||
{
|
||||
_previousSelectedTextRange = textRange;
|
||||
}
|
||||
|
||||
#pragma mark - Generalization
|
||||
|
||||
- (void)textViewProbablyDidChangeSelection
|
||||
{
|
||||
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
|
||||
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
|
||||
}
|
||||
|
||||
@end
|
48
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h
generated
vendored
Normal file
48
node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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>
|
||||
|
||||
@protocol RCTBackedTextInputDelegate;
|
||||
@class RCTTextAttributes;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol RCTBackedTextInputViewProtocol <UITextInput>
|
||||
|
||||
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
|
||||
@property (nonatomic, copy, nullable) NSString *placeholder;
|
||||
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
|
||||
@property (nonatomic, assign, readonly) BOOL textWasPasted;
|
||||
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
|
||||
@property (nonatomic, strong, nullable) UIView *inputAccessoryView;
|
||||
@property (nonatomic, weak, nullable) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
@property (nonatomic, readonly) CGSize contentSize;
|
||||
@property (nonatomic, strong, nullable) NSDictionary<NSAttributedStringKey,id> *defaultTextAttributes;
|
||||
@property (nonatomic, assign) BOOL contextMenuHidden;
|
||||
@property (nonatomic, assign, getter=isEditable) BOOL editable;
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign) BOOL enablesReturnKeyAutomatically;
|
||||
@property (nonatomic, assign) UITextFieldViewMode clearButtonMode;
|
||||
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
|
||||
|
||||
// This protocol disallows direct access to `selectedTextRange` property because
|
||||
// unwise usage of it can break the `delegate` behavior. So, we always have to
|
||||
// explicitly specify should `delegate` be notified about the change or not.
|
||||
// If the change was initiated programmatically, we must NOT notify the delegate.
|
||||
// If the change was a result of user actions (like typing or touches), we MUST notify the delegate.
|
||||
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange NS_UNAVAILABLE;
|
||||
- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate;
|
||||
|
||||
// This protocol disallows direct access to `text` property because
|
||||
// unwise usage of it can break the `attributeText` behavior.
|
||||
// Use `attributedText.string` instead.
|
||||
@property (nonatomic, copy, nullable) NSString *text NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
25
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
generated
vendored
Normal file
25
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextShadowView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTBaseTextInputShadowView : RCTBaseTextShadowView
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *text;
|
||||
@property (nonatomic, copy, nullable) NSString *placeholder;
|
||||
@property (nonatomic, assign) NSInteger maximumNumberOfLines;
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
|
||||
|
||||
- (void)uiManagerWillPerformMounting;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
326
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m
generated
vendored
Normal file
326
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m
generated
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTBaseTextInputShadowView.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <yoga/Yoga.h>
|
||||
|
||||
#import "NSTextStorage+FontScaling.h"
|
||||
#import <React/RCTBaseTextInputView.h>
|
||||
|
||||
@implementation RCTBaseTextInputShadowView
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
NSAttributedString *_Nullable _previousAttributedText;
|
||||
BOOL _needsUpdateView;
|
||||
NSAttributedString *_Nullable _localAttributedText;
|
||||
CGSize _previousContentSize;
|
||||
|
||||
NSString *_text;
|
||||
NSTextStorage *_textStorage;
|
||||
NSTextContainer *_textContainer;
|
||||
NSLayoutManager *_layoutManager;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
_needsUpdateView = YES;
|
||||
|
||||
YGNodeSetMeasureFunc(self.yogaNode, RCTBaseTextInputShadowViewMeasure);
|
||||
YGNodeSetBaselineFunc(self.yogaNode, RCTTextInputShadowViewBaseline);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isYogaLeafNode
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
|
||||
// `backgroundColor` and `opacity` are being applied directly to a UIView,
|
||||
// therefore we need to exclude them from base `textAttributes`.
|
||||
self.textAttributes.backgroundColor = nil;
|
||||
self.textAttributes.opacity = NAN;
|
||||
}
|
||||
|
||||
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
- (void)setLocalData:(NSObject *)localData
|
||||
{
|
||||
NSAttributedString *attributedText = (NSAttributedString *)localData;
|
||||
|
||||
if ([attributedText isEqualToAttributedString:_localAttributedText]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_localAttributedText = attributedText;
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
- (void)dirtyLayout
|
||||
{
|
||||
[super dirtyLayout];
|
||||
_needsUpdateView = YES;
|
||||
YGNodeMarkDirty(self.yogaNode);
|
||||
[self invalidateContentSize];
|
||||
}
|
||||
|
||||
- (void)invalidateContentSize
|
||||
{
|
||||
if (!_onContentSizeChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize maximumSize = self.layoutMetrics.frame.size;
|
||||
|
||||
if (_maximumNumberOfLines == 1) {
|
||||
maximumSize.width = CGFLOAT_MAX;
|
||||
} else {
|
||||
maximumSize.height = CGFLOAT_MAX;
|
||||
}
|
||||
|
||||
CGSize contentSize = [self sizeThatFitsMinimumSize:(CGSize)CGSizeZero maximumSize:maximumSize];
|
||||
|
||||
if (CGSizeEqualToSize(_previousContentSize, contentSize)) {
|
||||
return;
|
||||
}
|
||||
_previousContentSize = contentSize;
|
||||
|
||||
_onContentSizeChange(@{
|
||||
@"contentSize": @{
|
||||
@"height": @(contentSize.height),
|
||||
@"width": @(contentSize.width),
|
||||
},
|
||||
@"target": self.reactTag,
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)text
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
_text = text;
|
||||
// Clear `_previousAttributedText` to notify the view about the change
|
||||
// when `text` native prop is set.
|
||||
_previousAttributedText = nil;
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
#pragma mark - RCTUIManagerObserver
|
||||
|
||||
- (void)uiManagerWillPerformMounting
|
||||
{
|
||||
if (YGNodeIsDirty(self.yogaNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_needsUpdateView) {
|
||||
return;
|
||||
}
|
||||
_needsUpdateView = NO;
|
||||
|
||||
UIEdgeInsets borderInsets = self.borderAsInsets;
|
||||
UIEdgeInsets paddingInsets = self.paddingAsInsets;
|
||||
|
||||
RCTTextAttributes *textAttributes = [self.textAttributes copy];
|
||||
|
||||
NSMutableAttributedString *attributedText =
|
||||
[[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]];
|
||||
|
||||
// Removing all references to Shadow Views and tags to avoid unnecessary retaining
|
||||
// and problems with comparing the strings.
|
||||
[attributedText removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
|
||||
range:NSMakeRange(0, attributedText.length)];
|
||||
|
||||
[attributedText removeAttribute:RCTTextAttributesTagAttributeName
|
||||
range:NSMakeRange(0, attributedText.length)];
|
||||
|
||||
if (self.text.length) {
|
||||
NSAttributedString *propertyAttributedText =
|
||||
[[NSAttributedString alloc] initWithString:self.text
|
||||
attributes:self.textAttributes.effectiveTextAttributes];
|
||||
[attributedText insertAttributedString:propertyAttributedText atIndex:0];
|
||||
}
|
||||
|
||||
BOOL isAttributedTextChanged = NO;
|
||||
if (![_previousAttributedText isEqualToAttributedString:attributedText]) {
|
||||
// We have to follow `set prop` pattern:
|
||||
// If the value has not changed, we must not notify the view about the change,
|
||||
// otherwise we may break local (temporary) state of the text input.
|
||||
isAttributedTextChanged = YES;
|
||||
_previousAttributedText = [attributedText copy];
|
||||
}
|
||||
|
||||
NSNumber *tag = self.reactTag;
|
||||
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
RCTBaseTextInputView *baseTextInputView = (RCTBaseTextInputView *)viewRegistry[tag];
|
||||
if (!baseTextInputView) {
|
||||
return;
|
||||
}
|
||||
|
||||
baseTextInputView.textAttributes = textAttributes;
|
||||
baseTextInputView.reactBorderInsets = borderInsets;
|
||||
baseTextInputView.reactPaddingInsets = paddingInsets;
|
||||
|
||||
if (isAttributedTextChanged) {
|
||||
// Don't set `attributedText` if length equal to zero, otherwise it would shrink when attributes contain like `lineHeight`.
|
||||
if (attributedText.length != 0) {
|
||||
baseTextInputView.attributedText = attributedText;
|
||||
} else {
|
||||
baseTextInputView.attributedText = nil;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSAttributedString *)measurableAttributedText
|
||||
{
|
||||
// Only for the very first render when we don't have `_localAttributedText`,
|
||||
// we use value directly from the property and/or nested content.
|
||||
NSAttributedString *attributedText =
|
||||
_localAttributedText ?: [self attributedTextWithBaseTextAttributes:nil];
|
||||
|
||||
if (attributedText.length == 0) {
|
||||
// It's impossible to measure empty attributed string because all attributes are
|
||||
// associated with some characters, so no characters means no data.
|
||||
|
||||
// Placeholder also can represent the intrinsic size when it is visible.
|
||||
NSString *text = self.placeholder;
|
||||
if (!text.length) {
|
||||
// Note: `zero-width space` is insufficient in some cases.
|
||||
text = @"I";
|
||||
}
|
||||
attributedText = [[NSAttributedString alloc] initWithString:text attributes:self.textAttributes.effectiveTextAttributes];
|
||||
}
|
||||
|
||||
return attributedText;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
|
||||
{
|
||||
NSAttributedString *attributedText = [self measurableAttributedText];
|
||||
|
||||
if (!_textStorage) {
|
||||
_textContainer = [NSTextContainer new];
|
||||
_textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
|
||||
_layoutManager = [NSLayoutManager new];
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
_textStorage = [NSTextStorage new];
|
||||
[_textStorage addLayoutManager:_layoutManager];
|
||||
}
|
||||
|
||||
_textContainer.size = maximumSize;
|
||||
_textContainer.maximumNumberOfLines = _maximumNumberOfLines;
|
||||
[_textStorage replaceCharactersInRange:(NSRange){0, _textStorage.length}
|
||||
withAttributedString:attributedText];
|
||||
[_layoutManager ensureLayoutForTextContainer:_textContainer];
|
||||
CGSize size = [_layoutManager usedRectForTextContainer:_textContainer].size;
|
||||
|
||||
return (CGSize){
|
||||
MAX(minimumSize.width, MIN(RCTCeilPixelValue(size.width), maximumSize.width)),
|
||||
MAX(minimumSize.height, MIN(RCTCeilPixelValue(size.height), maximumSize.height))
|
||||
};
|
||||
}
|
||||
|
||||
- (CGFloat)lastBaselineForSize:(CGSize)size
|
||||
{
|
||||
NSAttributedString *attributedText = [self measurableAttributedText];
|
||||
|
||||
__block CGFloat maximumDescender = 0.0;
|
||||
|
||||
[attributedText enumerateAttribute:NSFontAttributeName
|
||||
inRange:NSMakeRange(0, attributedText.length)
|
||||
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
usingBlock:
|
||||
^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
||||
if (maximumDescender > font.descender) {
|
||||
maximumDescender = font.descender;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return size.height + maximumDescender;
|
||||
}
|
||||
|
||||
static YGSize RCTBaseTextInputShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
|
||||
{
|
||||
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
|
||||
|
||||
CGSize minimumSize = CGSizeMake(0, 0);
|
||||
CGSize maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
|
||||
CGSize size = {
|
||||
RCTCoreGraphicsFloatFromYogaFloat(width),
|
||||
RCTCoreGraphicsFloatFromYogaFloat(height)
|
||||
};
|
||||
|
||||
switch (widthMode) {
|
||||
case YGMeasureModeUndefined:
|
||||
break;
|
||||
case YGMeasureModeExactly:
|
||||
minimumSize.width = size.width;
|
||||
maximumSize.width = size.width;
|
||||
break;
|
||||
case YGMeasureModeAtMost:
|
||||
maximumSize.width = size.width;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case YGMeasureModeUndefined:
|
||||
break;
|
||||
case YGMeasureModeExactly:
|
||||
minimumSize.height = size.height;
|
||||
maximumSize.height = size.height;
|
||||
break;
|
||||
case YGMeasureModeAtMost:
|
||||
maximumSize.height = size.height;
|
||||
break;
|
||||
}
|
||||
|
||||
CGSize measuredSize = [shadowView sizeThatFitsMinimumSize:minimumSize maximumSize:maximumSize];
|
||||
|
||||
return (YGSize){
|
||||
RCTYogaFloatFromCoreGraphicsFloat(measuredSize.width),
|
||||
RCTYogaFloatFromCoreGraphicsFloat(measuredSize.height)
|
||||
};
|
||||
}
|
||||
|
||||
static float RCTTextInputShadowViewBaseline(YGNodeRef node, const float width, const float height)
|
||||
{
|
||||
RCTBaseTextInputShadowView *shadowTextView = (__bridge RCTBaseTextInputShadowView *)YGNodeGetContext(node);
|
||||
|
||||
CGSize size = (CGSize){
|
||||
RCTCoreGraphicsFloatFromYogaFloat(width),
|
||||
RCTCoreGraphicsFloatFromYogaFloat(height)
|
||||
};
|
||||
|
||||
CGFloat lastBaseline = [shadowTextView lastBaselineForSize:size];
|
||||
|
||||
return RCTYogaFloatFromCoreGraphicsFloat(lastBaseline);
|
||||
}
|
||||
|
||||
@end
|
63
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
generated
vendored
Normal file
63
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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/RCTView.h>
|
||||
|
||||
#import "RCTBackedTextInputDelegate.h"
|
||||
#import "RCTBackedTextInputViewProtocol.h"
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTEventDispatcher;
|
||||
@class RCTTextAttributes;
|
||||
@class RCTTextSelection;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTBaseTextInputView : RCTView <RCTBackedTextInputDelegate>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
|
||||
@property (nonatomic, readonly) UIView<RCTBackedTextInputViewProtocol> *backedTextInputView;
|
||||
|
||||
@property (nonatomic, strong, nullable) RCTTextAttributes *textAttributes;
|
||||
@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets;
|
||||
@property (nonatomic, assign) UIEdgeInsets reactBorderInsets;
|
||||
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onSelectionChange;
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onTextInput;
|
||||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
|
||||
|
||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||
@property (nonatomic, assign, readonly) NSInteger nativeEventCount;
|
||||
@property (nonatomic, assign) BOOL autoFocus;
|
||||
@property (nonatomic, assign) BOOL blurOnSubmit;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL clearTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL secureTextEntry;
|
||||
@property (nonatomic, copy) RCTTextSelection *selection;
|
||||
@property (nonatomic, strong, nullable) NSNumber *maxLength;
|
||||
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
|
||||
@property (nonatomic, copy) NSString *inputAccessoryViewID;
|
||||
@property (nonatomic, assign) UIKeyboardType keyboardType;
|
||||
|
||||
/**
|
||||
Sets selection intext input if both start and end are within range of the text input.
|
||||
**/
|
||||
- (void)setSelectionStart:(NSInteger)start
|
||||
selectionEnd:(NSInteger)end;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
680
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
generated
vendored
Normal file
680
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m
generated
vendored
Normal file
@ -0,0 +1,680 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTBaseTextInputView.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import <React/RCTInputAccessoryView.h>
|
||||
#import <React/RCTInputAccessoryViewContent.h>
|
||||
#import <React/RCTTextAttributes.h>
|
||||
#import <React/RCTTextSelection.h>
|
||||
|
||||
@implementation RCTBaseTextInputView {
|
||||
__weak RCTBridge *_bridge;
|
||||
__weak RCTEventDispatcher *_eventDispatcher;
|
||||
BOOL _hasInputAccesoryView;
|
||||
NSString *_Nullable _predictedText;
|
||||
BOOL _didMoveToWindow;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
RCTAssertParam(bridge);
|
||||
|
||||
if (self = [super initWithFrame:CGRectZero]) {
|
||||
_bridge = bridge;
|
||||
_eventDispatcher = bridge.eventDispatcher;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
|
||||
- (UIView<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
||||
{
|
||||
RCTAssert(NO, @"-[RCTBaseTextInputView backedTextInputView] must be implemented in subclass.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - RCTComponent
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setTextAttributes:(RCTTextAttributes *)textAttributes
|
||||
{
|
||||
_textAttributes = textAttributes;
|
||||
[self enforceTextAttributesIfNeeded];
|
||||
}
|
||||
|
||||
- (void)enforceTextAttributesIfNeeded
|
||||
{
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
||||
|
||||
NSDictionary<NSAttributedStringKey,id> *textAttributes = [[_textAttributes effectiveTextAttributes] mutableCopy];
|
||||
if ([textAttributes valueForKey:NSForegroundColorAttributeName] == nil) {
|
||||
[textAttributes setValue:[UIColor blackColor] forKey:NSForegroundColorAttributeName];
|
||||
}
|
||||
|
||||
backedTextInputView.defaultTextAttributes = textAttributes;
|
||||
}
|
||||
|
||||
- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
|
||||
{
|
||||
_reactPaddingInsets = reactPaddingInsets;
|
||||
// We apply `paddingInsets` as `backedTextInputView`'s `textContainerInset`.
|
||||
self.backedTextInputView.textContainerInset = reactPaddingInsets;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets
|
||||
{
|
||||
_reactBorderInsets = reactBorderInsets;
|
||||
// We apply `borderInsets` as `backedTextInputView` layout offset.
|
||||
self.backedTextInputView.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets);
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedText
|
||||
{
|
||||
return self.backedTextInputView.attributedText;
|
||||
}
|
||||
|
||||
- (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.
|
||||
// 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 =
|
||||
[self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] ||
|
||||
self.backedTextInputView.markedTextRange ||
|
||||
self.backedTextInputView.isSecureTextEntry ||
|
||||
fontHasBeenUpdatedBySystem;
|
||||
|
||||
if (shouldFallbackToBareTextComparison) {
|
||||
return ([newText.string isEqualToString:oldText.string]);
|
||||
} else {
|
||||
return ([newText isEqualToAttributedString:oldText]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText
|
||||
{
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
BOOL textNeedsUpdate = NO;
|
||||
// Remove tag attribute to ensure correct attributed string comparison.
|
||||
NSMutableAttributedString *const backedTextInputViewTextCopy = [self.backedTextInputView.attributedText mutableCopy];
|
||||
NSMutableAttributedString *const attributedTextCopy = [attributedText mutableCopy];
|
||||
|
||||
[backedTextInputViewTextCopy removeAttribute:RCTTextAttributesTagAttributeName
|
||||
range:NSMakeRange(0, backedTextInputViewTextCopy.length)];
|
||||
|
||||
[attributedTextCopy removeAttribute:RCTTextAttributesTagAttributeName
|
||||
range:NSMakeRange(0, attributedTextCopy.length)];
|
||||
|
||||
textNeedsUpdate = ([self textOf:attributedTextCopy equals:backedTextInputViewTextCopy] == NO);
|
||||
|
||||
if (eventLag == 0 && textNeedsUpdate) {
|
||||
UITextRange *selection = self.backedTextInputView.selectedTextRange;
|
||||
NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length;
|
||||
|
||||
self.backedTextInputView.attributedText = attributedText;
|
||||
|
||||
if (selection.empty) {
|
||||
// Maintaining a cursor position relative to the end of the old text.
|
||||
NSInteger offsetStart =
|
||||
[self.backedTextInputView offsetFromPosition:self.backedTextInputView.beginningOfDocument
|
||||
toPosition:selection.start];
|
||||
NSInteger offsetFromEnd = oldTextLength - offsetStart;
|
||||
NSInteger newOffset = attributedText.string.length - offsetFromEnd;
|
||||
UITextPosition *position =
|
||||
[self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument
|
||||
offset:newOffset];
|
||||
[self.backedTextInputView setSelectedTextRange:[self.backedTextInputView textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
}
|
||||
|
||||
[self updateLocalData];
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLog(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", self.backedTextInputView.attributedText.string, (long long)eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
- (RCTTextSelection *)selection
|
||||
{
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
||||
UITextRange *selectedTextRange = backedTextInputView.selectedTextRange;
|
||||
return [[RCTTextSelection new] initWithStart:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument toPosition:selectedTextRange.start]
|
||||
end:[backedTextInputView offsetFromPosition:backedTextInputView.beginningOfDocument toPosition:selectedTextRange.end]];
|
||||
}
|
||||
|
||||
- (void)setSelection:(RCTTextSelection *)selection
|
||||
{
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
||||
|
||||
UITextRange *previousSelectedTextRange = backedTextInputView.selectedTextRange;
|
||||
UITextPosition *start = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument offset:selection.start];
|
||||
UITextPosition *end = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument offset:selection.end];
|
||||
UITextRange *selectedTextRange = [backedTextInputView textRangeFromPosition:start toPosition:end];
|
||||
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![previousSelectedTextRange isEqual:selectedTextRange]) {
|
||||
[backedTextInputView setSelectedTextRange:selectedTextRange notifyDelegate:NO];
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLog(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", backedTextInputView.attributedText.string, (long long)eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectionStart:(NSInteger)start
|
||||
selectionEnd:(NSInteger)end
|
||||
{
|
||||
UITextPosition *startPosition = [self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument
|
||||
offset:start];
|
||||
UITextPosition *endPosition = [self.backedTextInputView positionFromPosition:self.backedTextInputView.beginningOfDocument
|
||||
offset:end];
|
||||
if (startPosition && endPosition) {
|
||||
UITextRange *range = [self.backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition];
|
||||
[self.backedTextInputView setSelectedTextRange:range notifyDelegate:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextContentType:(NSString *)type
|
||||
{
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
|
||||
static dispatch_once_t onceToken;
|
||||
static NSDictionary<NSString *, NSString *> *contentTypeMap;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
contentTypeMap = @{@"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,
|
||||
};
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
NSDictionary<NSString *, NSString *> * iOS11extras = @{@"username": UITextContentTypeUsername,
|
||||
@"password": UITextContentTypePassword};
|
||||
|
||||
NSMutableDictionary<NSString *, NSString *> * iOS11baseMap = [contentTypeMap mutableCopy];
|
||||
[iOS11baseMap addEntriesFromDictionary:iOS11extras];
|
||||
|
||||
contentTypeMap = [iOS11baseMap copy];
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000 /* __IPHONE_12_0 */
|
||||
if (@available(iOS 12.0, tvOS 12.0, *)) {
|
||||
NSDictionary<NSString *, NSString *> * iOS12extras = @{@"newPassword": UITextContentTypeNewPassword,
|
||||
@"oneTimeCode": UITextContentTypeOneTimeCode};
|
||||
|
||||
NSMutableDictionary<NSString *, NSString *> * iOS12baseMap = [contentTypeMap mutableCopy];
|
||||
[iOS12baseMap addEntriesFromDictionary:iOS12extras];
|
||||
|
||||
contentTypeMap = [iOS12baseMap copy];
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
// Setting textContentType to an empty string will disable any
|
||||
// default behaviour, like the autofill bar for password inputs
|
||||
self.backedTextInputView.textContentType = contentTypeMap[type] ?: type;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
- (void)setPasswordRules:(NSString *)descriptor
|
||||
{
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0
|
||||
if (@available(iOS 12.0, *)) {
|
||||
self.backedTextInputView.passwordRules = [UITextInputPasswordRules passwordRulesWithDescriptor:descriptor];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (UIKeyboardType)keyboardType
|
||||
{
|
||||
return self.backedTextInputView.keyboardType;
|
||||
}
|
||||
|
||||
- (void)setKeyboardType:(UIKeyboardType)keyboardType
|
||||
{
|
||||
UIView<RCTBackedTextInputViewProtocol> *textInputView = self.backedTextInputView;
|
||||
if (textInputView.keyboardType != keyboardType) {
|
||||
textInputView.keyboardType = keyboardType;
|
||||
// Without the call to reloadInputViews, the keyboard will not change until the textview field (the first responder) loses and regains focus.
|
||||
if (textInputView.isFirstResponder) {
|
||||
[textInputView reloadInputViews];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTBackedTextInputDelegate
|
||||
|
||||
- (BOOL)textInputShouldBeginEditing
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDidBeginEditing
|
||||
{
|
||||
if (_clearTextOnFocus) {
|
||||
self.backedTextInputView.attributedText = [NSAttributedString new];
|
||||
}
|
||||
|
||||
if (_selectTextOnFocus) {
|
||||
[self.backedTextInputView selectAll:nil];
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.attributedText.string
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (BOOL)textInputShouldEndEditing
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDidEndEditing
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.attributedText.string
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.attributedText.string
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (BOOL)textInputShouldReturn
|
||||
{
|
||||
// We send `submit` event here, in `textInputShouldReturn`
|
||||
// (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).
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
|
||||
reactTag:self.reactTag
|
||||
text:self.backedTextInputView.attributedText.string
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
return _blurOnSubmit;
|
||||
}
|
||||
|
||||
- (void)textInputDidReturn
|
||||
{
|
||||
// Does nothing.
|
||||
}
|
||||
|
||||
- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
|
||||
{
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
||||
|
||||
if (!backedTextInputView.textWasPasted) {
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
|
||||
reactTag:self.reactTag
|
||||
text:nil
|
||||
key:text
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
if (_maxLength) {
|
||||
NSInteger allowedLength = MAX(_maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length, 0);
|
||||
|
||||
if (text.length > allowedLength) {
|
||||
// If we typed/pasted more than one character, limit the text inputted.
|
||||
if (text.length > 1) {
|
||||
// Truncate the input string so the result is exactly maxLength
|
||||
NSString *limitedString = [text substringToIndex:allowedLength];
|
||||
NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy];
|
||||
// Apply text attributes if original input view doesn't have text.
|
||||
if (backedTextInputView.attributedText.length == 0) {
|
||||
newAttributedText = [[NSMutableAttributedString alloc] initWithString:[self.textAttributes applyTextAttributesToText:limitedString] attributes:self.textAttributes.effectiveTextAttributes];
|
||||
} else {
|
||||
[newAttributedText replaceCharactersInRange:range withString:limitedString];
|
||||
}
|
||||
backedTextInputView.attributedText = newAttributedText;
|
||||
_predictedText = newAttributedText.string;
|
||||
|
||||
// Collapse selection at end of insert to match normal paste behavior.
|
||||
UITextPosition *insertEnd = [backedTextInputView positionFromPosition:backedTextInputView.beginningOfDocument
|
||||
offset:(range.location + allowedLength)];
|
||||
[backedTextInputView setSelectedTextRange:[backedTextInputView textRangeFromPosition:insertEnd toPosition:insertEnd]
|
||||
notifyDelegate:YES];
|
||||
|
||||
[self textInputDidChange];
|
||||
}
|
||||
|
||||
return nil; // Rejecting the change.
|
||||
}
|
||||
}
|
||||
|
||||
NSString *previousText = backedTextInputView.attributedText.string ?: @"";
|
||||
|
||||
if (range.location + range.length > backedTextInputView.attributedText.string.length) {
|
||||
_predictedText = backedTextInputView.attributedText.string;
|
||||
} else {
|
||||
_predictedText = [backedTextInputView.attributedText.string stringByReplacingCharactersInRange:range withString:text];
|
||||
}
|
||||
|
||||
if (_onTextInput) {
|
||||
_onTextInput(@{
|
||||
@"text": text,
|
||||
@"previousText": previousText,
|
||||
@"range": @{
|
||||
@"start": @(range.location),
|
||||
@"end": @(range.location + range.length)
|
||||
},
|
||||
@"eventCount": @(_nativeEventCount),
|
||||
});
|
||||
}
|
||||
|
||||
return text; // Accepting the change.
|
||||
}
|
||||
|
||||
- (void)textInputDidChange
|
||||
{
|
||||
[self updateLocalData];
|
||||
|
||||
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
||||
|
||||
// Detect when `backedTextInputView` updates happened that didn't invoke `shouldChangeTextInRange`
|
||||
// (e.g. typing simplified Chinese in pinyin will insert and remove spaces without
|
||||
// calling shouldChangeTextInRange). This will cause JS to get out of sync so we
|
||||
// update the mismatched range.
|
||||
NSRange currentRange;
|
||||
NSRange predictionRange;
|
||||
if (findMismatch(backedTextInputView.attributedText.string, _predictedText, ¤tRange, &predictionRange)) {
|
||||
NSString *replacement = [backedTextInputView.attributedText.string substringWithRange:currentRange];
|
||||
[self textInputShouldChangeText:replacement inRange:predictionRange];
|
||||
// JS will assume the selection changed based on the location of our shouldChangeTextInRange, so reset it.
|
||||
[self textInputDidChangeSelection];
|
||||
}
|
||||
|
||||
_nativeEventCount++;
|
||||
|
||||
if (_onChange) {
|
||||
_onChange(@{
|
||||
@"text": self.attributedText.string,
|
||||
@"target": self.reactTag,
|
||||
@"eventCount": @(_nativeEventCount),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textInputDidChangeSelection
|
||||
{
|
||||
if (!_onSelectionChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTTextSelection *selection = self.selection;
|
||||
|
||||
_onSelectionChange(@{
|
||||
@"selection": @{
|
||||
@"start": @(selection.start),
|
||||
@"end": @(selection.end),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateLocalData
|
||||
{
|
||||
[self enforceTextAttributesIfNeeded];
|
||||
|
||||
[_bridge.uiManager setLocalData:[self.backedTextInputView.attributedText copy]
|
||||
forView:self];
|
||||
}
|
||||
|
||||
#pragma mark - Layout (in UIKit terms, with all insets)
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
CGSize size = self.backedTextInputView.intrinsicContentSize;
|
||||
size.width += _reactBorderInsets.left + _reactBorderInsets.right;
|
||||
size.height += _reactBorderInsets.top + _reactBorderInsets.bottom;
|
||||
// Returning value DOES include border and padding insets.
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
CGFloat compoundHorizontalBorderInset = _reactBorderInsets.left + _reactBorderInsets.right;
|
||||
CGFloat compoundVerticalBorderInset = _reactBorderInsets.top + _reactBorderInsets.bottom;
|
||||
|
||||
size.width -= compoundHorizontalBorderInset;
|
||||
size.height -= compoundVerticalBorderInset;
|
||||
|
||||
// Note: `paddingInsets` was already included in `backedTextInputView` size
|
||||
// because it was applied as `textContainerInset`.
|
||||
CGSize fittingSize = [self.backedTextInputView sizeThatFits:size];
|
||||
|
||||
fittingSize.width += compoundHorizontalBorderInset;
|
||||
fittingSize.height += compoundVerticalBorderInset;
|
||||
|
||||
// Returning value DOES include border and padding insets.
|
||||
return fittingSize;
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (UIView *)reactAccessibilityElement
|
||||
{
|
||||
return self.backedTextInputView;
|
||||
}
|
||||
|
||||
#pragma mark - Focus Control
|
||||
|
||||
- (void)reactFocus
|
||||
{
|
||||
[self.backedTextInputView reactFocus];
|
||||
}
|
||||
|
||||
- (void)reactBlur
|
||||
{
|
||||
[self.backedTextInputView reactBlur];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
if (self.autoFocus && !_didMoveToWindow) {
|
||||
[self.backedTextInputView reactFocus];
|
||||
} else {
|
||||
[self.backedTextInputView reactFocusIfNeeded];
|
||||
}
|
||||
|
||||
_didMoveToWindow = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Custom Input Accessory View
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) {
|
||||
[self setCustomInputAccessoryViewWithNativeID:self.inputAccessoryViewID];
|
||||
} else if (!self.inputAccessoryViewID) {
|
||||
[self setDefaultInputAccessoryView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID
|
||||
{
|
||||
#if !TARGET_OS_TV
|
||||
__weak RCTBaseTextInputView *weakSelf = self;
|
||||
[_bridge.uiManager rootViewForReactTag:self.reactTag withCompletion:^(UIView *rootView) {
|
||||
RCTBaseTextInputView *strongSelf = weakSelf;
|
||||
if (rootView) {
|
||||
UIView *accessoryView = [strongSelf->_bridge.uiManager viewForNativeID:nativeID
|
||||
withRootTag:rootView.reactTag];
|
||||
if (accessoryView && [accessoryView isKindOfClass:[RCTInputAccessoryView class]]) {
|
||||
strongSelf.backedTextInputView.inputAccessoryView = ((RCTInputAccessoryView *)accessoryView).inputAccessoryView;
|
||||
[strongSelf reloadInputViewsIfNecessary];
|
||||
}
|
||||
}
|
||||
}];
|
||||
#endif /* !TARGET_OS_TV */
|
||||
}
|
||||
|
||||
- (void)setDefaultInputAccessoryView
|
||||
{
|
||||
#if !TARGET_OS_TV
|
||||
UIView<RCTBackedTextInputViewProtocol> *textInputView = self.backedTextInputView;
|
||||
UIKeyboardType keyboardType = textInputView.keyboardType;
|
||||
|
||||
// These keyboard types (all are number pads) don't have a "Done" button by default,
|
||||
// so we create an `inputAccessoryView` with this button for them.
|
||||
BOOL shouldHaveInputAccesoryView;
|
||||
if (@available(iOS 10.0, *)) {
|
||||
shouldHaveInputAccesoryView =
|
||||
(
|
||||
keyboardType == UIKeyboardTypeNumberPad ||
|
||||
keyboardType == UIKeyboardTypePhonePad ||
|
||||
keyboardType == UIKeyboardTypeDecimalPad ||
|
||||
keyboardType == UIKeyboardTypeASCIICapableNumberPad
|
||||
) &&
|
||||
textInputView.returnKeyType == UIReturnKeyDone;
|
||||
} else {
|
||||
shouldHaveInputAccesoryView =
|
||||
(
|
||||
keyboardType == UIKeyboardTypeNumberPad ||
|
||||
keyboardType == UIKeyboardTypePhonePad ||
|
||||
keyboardType == UIKeyboardTypeDecimalPad
|
||||
) &&
|
||||
textInputView.returnKeyType == UIReturnKeyDone;
|
||||
}
|
||||
|
||||
if (_hasInputAccesoryView == shouldHaveInputAccesoryView) {
|
||||
return;
|
||||
}
|
||||
|
||||
_hasInputAccesoryView = shouldHaveInputAccesoryView;
|
||||
|
||||
if (shouldHaveInputAccesoryView) {
|
||||
UIToolbar *toolbarView = [[UIToolbar alloc] init];
|
||||
[toolbarView sizeToFit];
|
||||
UIBarButtonItem *flexibleSpace =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
|
||||
target:nil
|
||||
action:nil];
|
||||
UIBarButtonItem *doneButton =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
|
||||
target:self
|
||||
action:@selector(handleInputAccessoryDoneButton)];
|
||||
toolbarView.items = @[flexibleSpace, doneButton];
|
||||
textInputView.inputAccessoryView = toolbarView;
|
||||
}
|
||||
else {
|
||||
textInputView.inputAccessoryView = nil;
|
||||
}
|
||||
[self reloadInputViewsIfNecessary];
|
||||
#endif /* !TARGET_OS_TV */
|
||||
}
|
||||
|
||||
- (void)reloadInputViewsIfNecessary
|
||||
{
|
||||
// We have to call `reloadInputViews` for focused text inputs to update an accessory view.
|
||||
if (self.backedTextInputView.isFirstResponder) {
|
||||
[self.backedTextInputView reloadInputViews];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleInputAccessoryDoneButton
|
||||
{
|
||||
if ([self textInputShouldReturn]) {
|
||||
[self.backedTextInputView endEditing:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange)
|
||||
{
|
||||
NSInteger firstMismatch = -1;
|
||||
for (NSUInteger ii = 0; ii < MAX(first.length, second.length); ii++) {
|
||||
if (ii >= first.length || ii >= second.length || [first characterAtIndex:ii] != [second characterAtIndex:ii]) {
|
||||
firstMismatch = ii;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMismatch == -1) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSUInteger ii = second.length;
|
||||
NSUInteger lastMismatch = first.length;
|
||||
while (ii > firstMismatch && lastMismatch > firstMismatch) {
|
||||
if ([first characterAtIndex:(lastMismatch - 1)] != [second characterAtIndex:(ii - 1)]) {
|
||||
break;
|
||||
}
|
||||
ii--;
|
||||
lastMismatch--;
|
||||
}
|
||||
|
||||
*firstRange = NSMakeRange(firstMismatch, lastMismatch - firstMismatch);
|
||||
*secondRange = NSMakeRange(firstMismatch, ii - firstMismatch);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
12
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextViewManager.h"
|
||||
|
||||
@interface RCTBaseTextInputViewManager : RCTBaseTextViewManager
|
||||
|
||||
@end
|
170
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m
generated
vendored
Normal file
170
node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTBaseTextInputViewManager.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUIManagerUtils.h>
|
||||
#import <React/RCTUIManagerObserverCoordinator.h>
|
||||
|
||||
#import <React/RCTBaseTextInputShadowView.h>
|
||||
#import <React/RCTBaseTextInputView.h>
|
||||
#import <React/RCTConvert+Text.h>
|
||||
|
||||
@interface RCTBaseTextInputViewManager () <RCTUIManagerObserver>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBaseTextInputViewManager
|
||||
{
|
||||
NSHashTable<RCTBaseTextInputShadowView *> *_shadowViews;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - Unified <TextInput> properties
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
|
||||
RCT_REMAP_VIEW_PROPERTY(contextMenuHidden, backedTextInputView.contextMenuHidden, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
|
||||
RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(placeholderTextColor, backedTextInputView.placeholderColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(returnKeyType, backedTextInputView.returnKeyType, UIReturnKeyType)
|
||||
RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
|
||||
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
|
||||
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_EXPORT_VIEW_PROPERTY(autoFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
|
||||
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(textContentType, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTBubblingEventBlock)
|
||||
|
||||
RCT_CUSTOM_VIEW_PROPERTY(multiline, BOOL, UIView)
|
||||
{
|
||||
// No op.
|
||||
// This View Manager doesn't use this prop but it must be exposed here via ViewConfig to enable Fabric component use it.
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
RCTBaseTextInputShadowView *shadowView = [[RCTBaseTextInputShadowView alloc] initWithBridge:self.bridge];
|
||||
shadowView.textAttributes.fontSizeMultiplier = [[[self.bridge
|
||||
moduleForName:@"AccessibilityManager"
|
||||
lazilyLoadIfNecessary:YES] valueForKey:@"multiplier"] floatValue];
|
||||
[_shadowViews addObject:shadowView];
|
||||
return shadowView;
|
||||
}
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
[super setBridge:bridge];
|
||||
|
||||
_shadowViews = [NSHashTable weakObjectsHashTable];
|
||||
|
||||
[bridge.uiManager.observerCoordinator addObserver:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleDidUpdateMultiplierNotification)
|
||||
name:@"RCTAccessibilityManagerDidUpdateMultiplierNotification"
|
||||
object:[bridge moduleForName:@"AccessibilityManager"
|
||||
lazilyLoadIfNecessary:YES]];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)viewTag)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
UIView *view = viewRegistry[viewTag];
|
||||
[view reactFocus];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)viewTag)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
UIView *view = viewRegistry[viewTag];
|
||||
[view reactBlur];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setTextAndSelection : (nonnull NSNumber *)viewTag
|
||||
mostRecentEventCount : (NSInteger)mostRecentEventCount
|
||||
value : (NSString *)value
|
||||
start : (NSInteger)start
|
||||
end : (NSInteger)end)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
RCTBaseTextInputView *view = (RCTBaseTextInputView *)viewRegistry[viewTag];
|
||||
NSInteger eventLag = view.nativeEventCount - mostRecentEventCount;
|
||||
if (eventLag != 0) {
|
||||
return;
|
||||
}
|
||||
RCTExecuteOnUIManagerQueue(^{
|
||||
RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[self.bridge.uiManager shadowViewForReactTag:viewTag];
|
||||
[shadowView setText:value];
|
||||
[self.bridge.uiManager setNeedsLayout];
|
||||
RCTExecuteOnMainQueue(^{
|
||||
[view setSelectionStart:start selectionEnd:end];
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - RCTUIManagerObserver
|
||||
|
||||
- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager
|
||||
{
|
||||
for (RCTBaseTextInputShadowView *shadowView in _shadowViews) {
|
||||
[shadowView uiManagerWillPerformMounting];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Font Size Multiplier
|
||||
|
||||
- (void)handleDidUpdateMultiplierNotification
|
||||
{
|
||||
CGFloat fontSizeMultiplier = [[[self.bridge moduleForName:@"AccessibilityManager"]
|
||||
valueForKey:@"multiplier"] floatValue];
|
||||
|
||||
NSHashTable<RCTBaseTextInputShadowView *> *shadowViews = _shadowViews;
|
||||
RCTExecuteOnUIManagerQueue(^{
|
||||
for (RCTBaseTextInputShadowView *shadowView in shadowViews) {
|
||||
shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier;
|
||||
[shadowView dirtyLayout];
|
||||
}
|
||||
|
||||
[self.bridge.uiManager setNeedsLayout];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTShadowView.h>
|
||||
|
||||
@interface RCTInputAccessoryShadowView : RCTShadowView
|
||||
|
||||
@end
|
20
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.m
generated
vendored
Normal file
20
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryShadowView.m
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTInputAccessoryShadowView.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@implementation RCTInputAccessoryShadowView
|
||||
|
||||
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
subview.width = (YGValue) { RCTScreenSize().width, YGUnitPoint };
|
||||
}
|
||||
|
||||
@end
|
17
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h
generated
vendored
Normal file
17
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.h
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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>
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTInputAccessoryViewContent;
|
||||
|
||||
@interface RCTInputAccessoryView : UIView
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@end
|
79
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.m
generated
vendored
Normal file
79
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryView.m
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTInputAccessoryView.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import <React/RCTInputAccessoryViewContent.h>
|
||||
|
||||
@interface RCTInputAccessoryView()
|
||||
|
||||
// Overriding `inputAccessoryView` to `readwrite`.
|
||||
@property (nonatomic, readwrite, retain) UIView *inputAccessoryView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTInputAccessoryView
|
||||
{
|
||||
BOOL _shouldBecomeFirstResponder;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_inputAccessoryView = [RCTInputAccessoryViewContent new];
|
||||
RCTTouchHandler *const touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
|
||||
[touchHandler attachToView:_inputAccessoryView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
[_inputAccessoryView reactSetFrame:frame];
|
||||
|
||||
if (_shouldBecomeFirstResponder) {
|
||||
_shouldBecomeFirstResponder = NO;
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:index];
|
||||
[_inputAccessoryView insertReactSubview:subview atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[_inputAccessoryView removeReactSubview:subview];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are managed by `insertReactSubview:atIndex:`.
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
// If the accessory view is not linked to a text input via nativeID, assume it is
|
||||
// a standalone component that should get focus whenever it is rendered.
|
||||
if (![changedProps containsObject:@"nativeID"] && !self.nativeID) {
|
||||
_shouldBecomeFirstResponder = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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>
|
||||
|
||||
@interface RCTInputAccessoryViewContent : UIView
|
||||
|
||||
@end
|
82
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m
generated
vendored
Normal file
82
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTInputAccessoryViewContent.h>
|
||||
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
@implementation RCTInputAccessoryViewContent
|
||||
{
|
||||
UIView *_safeAreaContainer;
|
||||
NSLayoutConstraint *_heightConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_safeAreaContainer = [UIView new];
|
||||
[self addSubview:_safeAreaContainer];
|
||||
|
||||
// Use autolayout to position the view properly and take into account
|
||||
// safe area insets on iPhone X.
|
||||
// TODO: Support rotation, anchor to left and right without breaking frame x coordinate (T27974328).
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleHeight;
|
||||
_safeAreaContainer.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
_heightConstraint = [_safeAreaContainer.heightAnchor constraintEqualToConstant:0];
|
||||
_heightConstraint.active = YES;
|
||||
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
[_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES;
|
||||
[_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES;
|
||||
[_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor].active = YES;
|
||||
[_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor].active = YES;
|
||||
} else {
|
||||
[_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
|
||||
[_safeAreaContainer.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
|
||||
[_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES;
|
||||
[_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
// This is needed so the view size is based on autolayout constraints.
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
// We still need to set the frame here, otherwise it won't be
|
||||
// measured until moved to the window during the keyboard opening
|
||||
// animation. If this happens, the height will be animated from 0 to
|
||||
// its actual size and we don't want that.
|
||||
[self setFrame:frame];
|
||||
[_safeAreaContainer setFrame:frame];
|
||||
|
||||
_heightConstraint.constant = frame.size.height;
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:index];
|
||||
[_safeAreaContainer insertSubview:subview atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[subview removeFromSuperview];
|
||||
if ([[_safeAreaContainer subviews] count] == 0 && [self isFirstResponder]) {
|
||||
[self resignFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.h
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface RCTInputAccessoryViewManager : RCTViewManager
|
||||
|
||||
@end
|
34
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.m
generated
vendored
Normal file
34
node_modules/react-native/Libraries/Text/TextInput/RCTInputAccessoryViewManager.m
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTInputAccessoryViewManager.h>
|
||||
|
||||
#import <React/RCTInputAccessoryShadowView.h>
|
||||
#import <React/RCTInputAccessoryView.h>
|
||||
|
||||
@implementation RCTInputAccessoryViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTInputAccessoryView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [RCTInputAccessoryShadowView new];
|
||||
}
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(backgroundColor, inputAccessoryView.backgroundColor, UIColor)
|
||||
|
||||
@end
|
26
node_modules/react-native/Libraries/Text/TextInput/RCTTextSelection.h
generated
vendored
Normal file
26
node_modules/react-native/Libraries/Text/TextInput/RCTTextSelection.h
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
/**
|
||||
* Object containing information about a TextInput's selection.
|
||||
*/
|
||||
@interface RCTTextSelection : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSInteger start;
|
||||
@property (nonatomic, assign, readonly) NSInteger end;
|
||||
|
||||
- (instancetype)initWithStart:(NSInteger)start end:(NSInteger)end;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTConvert (RCTTextSelection)
|
||||
|
||||
+ (RCTTextSelection *)RCTTextSelection:(id)json;
|
||||
|
||||
@end
|
36
node_modules/react-native/Libraries/Text/TextInput/RCTTextSelection.m
generated
vendored
Normal file
36
node_modules/react-native/Libraries/Text/TextInput/RCTTextSelection.m
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTTextSelection.h>
|
||||
|
||||
@implementation RCTTextSelection
|
||||
|
||||
- (instancetype)initWithStart:(NSInteger)start end:(NSInteger)end
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConvert (RCTTextSelection)
|
||||
|
||||
+ (RCTTextSelection *)RCTTextSelection:(id)json
|
||||
{
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
NSInteger start = [self NSInteger:json[@"start"]];
|
||||
NSInteger end = [self NSInteger:json[@"end"]];
|
||||
return [[RCTTextSelection alloc] initWithStart:start end:end];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.h
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextInputView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTSinglelineTextInputView : RCTBaseTextInputView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
40
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m
generated
vendored
Normal file
40
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTSinglelineTextInputView.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
#import <React/RCTUITextField.h>
|
||||
|
||||
@implementation RCTSinglelineTextInputView
|
||||
{
|
||||
RCTUITextField *_backedTextInputView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
// `blurOnSubmit` defaults to `true` for <TextInput multiline={false}> by design.
|
||||
self.blurOnSubmit = YES;
|
||||
|
||||
_backedTextInputView = [[RCTUITextField alloc] initWithFrame:self.bounds];
|
||||
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInputView.textInputDelegate = self;
|
||||
|
||||
[self addSubview:_backedTextInputView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
return _backedTextInputView;
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.h
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTBaseTextInputViewManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTSinglelineTextInputViewManager : RCTBaseTextInputViewManager
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
32
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m
generated
vendored
Normal file
32
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTSinglelineTextInputViewManager.h>
|
||||
|
||||
#import <React/RCTBaseTextInputShadowView.h>
|
||||
#import <React/RCTSinglelineTextInputView.h>
|
||||
|
||||
@implementation RCTSinglelineTextInputViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
RCTBaseTextInputShadowView *shadowView =
|
||||
(RCTBaseTextInputShadowView *)[super shadowView];
|
||||
|
||||
shadowView.maximumNumberOfLines = 1;
|
||||
|
||||
return shadowView;
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTSinglelineTextInputView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
@end
|
34
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h
generated
vendored
Normal file
34
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its 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/RCTBackedTextInputViewProtocol.h>
|
||||
#import <React/RCTBackedTextInputDelegate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
* Just regular UITextField... but much better!
|
||||
*/
|
||||
@interface RCTUITextField : UITextField <RCTBackedTextInputViewProtocol>
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
|
||||
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign) BOOL contextMenuHidden;
|
||||
@property (nonatomic, assign, readonly) BOOL textWasPasted;
|
||||
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
|
||||
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
|
||||
@property (nonatomic, assign, getter=isEditable) BOOL editable;
|
||||
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
222
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m
generated
vendored
Normal file
222
node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m
generated
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTUITextField.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
||||
#import <React/RCTTextAttributes.h>
|
||||
|
||||
@implementation RCTUITextField {
|
||||
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
|
||||
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(_textDidChange)
|
||||
name:UITextFieldTextDidChangeNotification
|
||||
object:self];
|
||||
|
||||
_textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self];
|
||||
_scrollEnabled = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_textDidChange
|
||||
{
|
||||
_textWasPasted = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
|
||||
{
|
||||
// UITextField is accessible by default (some nested views are) and disabling that is not supported.
|
||||
// On iOS accessible elements cannot be nested, therefore enabling accessibility for some container view
|
||||
// (even in a case where this view is a part of public API of TextInput on iOS) shadows some features implemented inside the component.
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
|
||||
{
|
||||
_textContainerInset = textContainerInset;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
[super setPlaceholder:placeholder];
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderColor:(UIColor *)placeholderColor
|
||||
{
|
||||
_placeholderColor = placeholderColor;
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setDefaultTextAttributes:(NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
||||
{
|
||||
if ([_defaultTextAttributes isEqualToDictionary:defaultTextAttributes]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultTextAttributes = defaultTextAttributes;
|
||||
[super setDefaultTextAttributes:defaultTextAttributes];
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
||||
{
|
||||
return _defaultTextAttributes;
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholder
|
||||
{
|
||||
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder ?: @""
|
||||
attributes:[self _placeholderTextAttributes]];
|
||||
}
|
||||
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return self.isEnabled;
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)editable
|
||||
{
|
||||
self.enabled = editable;
|
||||
}
|
||||
|
||||
- (void)setSecureTextEntry:(BOOL)secureTextEntry
|
||||
{
|
||||
if (self.secureTextEntry == secureTextEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
[super setSecureTextEntry:secureTextEntry];
|
||||
|
||||
// Fix for trailing whitespate issue
|
||||
// Read more:
|
||||
// https://stackoverflow.com/questions/14220187/uitextfield-has-trailing-whitespace-after-securetextentry-toggle/22537788#22537788
|
||||
NSAttributedString *originalText = [self.attributedText copy];
|
||||
self.attributedText = [NSAttributedString new];
|
||||
self.attributedText = originalText;
|
||||
}
|
||||
|
||||
#pragma mark - Placeholder
|
||||
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)_placeholderTextAttributes
|
||||
{
|
||||
NSMutableDictionary<NSAttributedStringKey, id> *textAttributes = [_defaultTextAttributes mutableCopy] ?: [NSMutableDictionary new];
|
||||
|
||||
if (self.placeholderColor) {
|
||||
[textAttributes setValue:self.placeholderColor forKey:NSForegroundColorAttributeName];
|
||||
} else {
|
||||
[textAttributes removeObjectForKey:NSForegroundColorAttributeName];
|
||||
}
|
||||
|
||||
return textAttributes;
|
||||
}
|
||||
|
||||
#pragma mark - Context Menu
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
if (_contextMenuHidden) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
#pragma mark - Caret Manipulation
|
||||
|
||||
- (CGRect)caretRectForPosition:(UITextPosition *)position
|
||||
{
|
||||
if (_caretHidden) {
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
return [super caretRectForPosition:position];
|
||||
}
|
||||
|
||||
#pragma mark - Positioning Overrides
|
||||
|
||||
- (CGRect)textRectForBounds:(CGRect)bounds
|
||||
{
|
||||
return UIEdgeInsetsInsetRect([super textRectForBounds:bounds], _textContainerInset);
|
||||
}
|
||||
|
||||
- (CGRect)editingRectForBounds:(CGRect)bounds
|
||||
{
|
||||
return [self textRectForBounds:bounds];
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
|
||||
// Overrides selectedTextRange setter to get notify when selectedTextRange changed.
|
||||
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange
|
||||
{
|
||||
[super setSelectedTextRange:selectedTextRange];
|
||||
[_textInputDelegateAdapter selectedTextRangeWasSet];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
|
||||
{
|
||||
if (!notifyDelegate) {
|
||||
// We have to notify an adapter that following selection change was initiated programmatically,
|
||||
// so the adapter must not generate a notification for it.
|
||||
[_textInputDelegateAdapter skipNextTextInputDidChangeSelectionEventWithTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
[super setSelectedTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender
|
||||
{
|
||||
[super paste:sender];
|
||||
_textWasPasted = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return self.intrinsicContentSize;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
// Note: `placeholder` defines intrinsic size for `<TextInput>`.
|
||||
NSString *text = self.placeholder ?: @"";
|
||||
CGSize size = [text sizeWithAttributes:[self _placeholderTextAttributes]];
|
||||
size = CGSizeMake(RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height));
|
||||
size.width += _textContainerInset.left + _textContainerInset.right;
|
||||
size.height += _textContainerInset.top + _textContainerInset.bottom;
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
// All size values here contain `textContainerInset` (aka `padding`).
|
||||
CGSize intrinsicSize = self.intrinsicContentSize;
|
||||
return CGSizeMake(MIN(size.width, intrinsicSize.width), MIN(size.height, intrinsicSize.height));
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user