yeet
This commit is contained in:
27
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h
generated
vendored
Normal file
27
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.h
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#import "RCTTextAttributes.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName;
|
||||
|
||||
@interface RCTBaseTextShadowView : RCTShadowView {
|
||||
@protected NSAttributedString *_Nullable cachedAttributedText;
|
||||
@protected RCTTextAttributes *_Nullable cachedTextAttributes;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) RCTTextAttributes *textAttributes;
|
||||
|
||||
- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
157
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.m
generated
vendored
Normal file
157
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextShadowView.m
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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/RCTBaseTextShadowView.h>
|
||||
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
|
||||
#import <React/RCTRawTextShadowView.h>
|
||||
#import <React/RCTVirtualTextShadowView.h>
|
||||
|
||||
NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = @"RCTBaseTextShadowViewEmbeddedShadowViewAttributeName";
|
||||
|
||||
static void RCTInlineViewYogaNodeDirtied(YGNodeRef node)
|
||||
{
|
||||
// An inline view (a view nested inside of a text node) does not have a parent
|
||||
// in the Yoga tree. Consequently, we have to manually propagate the inline
|
||||
// view's dirty signal up through the text nodes. At some point, it'll reach
|
||||
// the outermost text node which has a Yoga node and then Yoga will take over
|
||||
// the dirty signal propagation.
|
||||
RCTShadowView *inlineView = (__bridge RCTShadowView *)YGNodeGetContext(node);
|
||||
RCTBaseTextShadowView *baseTextShadowView =
|
||||
(RCTBaseTextShadowView *)inlineView.reactSuperview;
|
||||
|
||||
[baseTextShadowView dirtyLayout];
|
||||
}
|
||||
|
||||
@implementation RCTBaseTextShadowView
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_textAttributes = [RCTTextAttributes new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setReactTag:(NSNumber *)reactTag
|
||||
{
|
||||
[super setReactTag:reactTag];
|
||||
_textAttributes.tag = reactTag;
|
||||
}
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)index
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:index];
|
||||
|
||||
[self dirtyLayout];
|
||||
|
||||
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
|
||||
YGNodeSetDirtiedFunc(subview.yogaNode, RCTInlineViewYogaNodeDirtied);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(RCTShadowView *)subview
|
||||
{
|
||||
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
|
||||
YGNodeSetDirtiedFunc(subview.yogaNode, NULL);
|
||||
}
|
||||
|
||||
[self dirtyLayout];
|
||||
|
||||
[super removeReactSubview:subview];
|
||||
}
|
||||
|
||||
#pragma mark - attributedString
|
||||
|
||||
- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes
|
||||
{
|
||||
RCTTextAttributes *textAttributes;
|
||||
|
||||
if (baseTextAttributes) {
|
||||
textAttributes = [baseTextAttributes copy];
|
||||
[textAttributes applyTextAttributes:self.textAttributes];
|
||||
} else {
|
||||
textAttributes = [self.textAttributes copy];
|
||||
}
|
||||
|
||||
if (cachedAttributedText && [cachedTextAttributes isEqual:textAttributes]) {
|
||||
return cachedAttributedText;
|
||||
}
|
||||
|
||||
NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
|
||||
|
||||
[attributedText beginEditing];
|
||||
|
||||
for (RCTShadowView *shadowView in self.reactSubviews) {
|
||||
// Special Case: RCTRawTextShadowView
|
||||
if ([shadowView isKindOfClass:[RCTRawTextShadowView class]]) {
|
||||
RCTRawTextShadowView *rawTextShadowView = (RCTRawTextShadowView *)shadowView;
|
||||
NSString *text = rawTextShadowView.text;
|
||||
if (text) {
|
||||
NSAttributedString *rawTextAttributedString =
|
||||
[[NSAttributedString alloc] initWithString:[textAttributes applyTextAttributesToText:text]
|
||||
attributes:textAttributes.effectiveTextAttributes];
|
||||
[attributedText appendAttributedString:rawTextAttributedString];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special Case: RCTBaseTextShadowView
|
||||
if ([shadowView isKindOfClass:[RCTBaseTextShadowView class]]) {
|
||||
RCTBaseTextShadowView *baseTextShadowView = (RCTBaseTextShadowView *)shadowView;
|
||||
NSAttributedString *baseTextAttributedString =
|
||||
[baseTextShadowView attributedTextWithBaseTextAttributes:textAttributes];
|
||||
[attributedText appendAttributedString:baseTextAttributedString];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generic Case: Any RCTShadowView
|
||||
NSTextAttachment *attachment = [NSTextAttachment new];
|
||||
NSMutableAttributedString *embeddedShadowViewAttributedString = [NSMutableAttributedString new];
|
||||
[embeddedShadowViewAttributedString beginEditing];
|
||||
[embeddedShadowViewAttributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
|
||||
[embeddedShadowViewAttributedString addAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
|
||||
value:shadowView
|
||||
range:(NSRange){0, embeddedShadowViewAttributedString.length}];
|
||||
[embeddedShadowViewAttributedString endEditing];
|
||||
[attributedText appendAttributedString:embeddedShadowViewAttributedString];
|
||||
}
|
||||
|
||||
[attributedText endEditing];
|
||||
|
||||
[self clearLayout];
|
||||
|
||||
cachedAttributedText = [attributedText copy];
|
||||
cachedTextAttributes = textAttributes;
|
||||
|
||||
return cachedAttributedText;
|
||||
}
|
||||
|
||||
- (void)dirtyLayout
|
||||
{
|
||||
[super dirtyLayout];
|
||||
cachedAttributedText = nil;
|
||||
cachedTextAttributes = nil;
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
[super didUpdateReactSubviews];
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
[self dirtyLayout];
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.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 <React/RCTViewManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTBaseTextViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
57
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.m
generated
vendored
Normal file
57
node_modules/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.m
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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/RCTBaseTextViewManager.h>
|
||||
|
||||
@implementation RCTBaseTextViewManager
|
||||
|
||||
RCT_EXPORT_MODULE(RCTBaseText)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
RCTAssert(NO, @"The `-[RCTBaseTextViewManager view]` property must be overridden in subclass.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
RCTAssert(NO, @"The `-[RCTBaseTextViewManager shadowView]` property must be overridden in subclass.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Text Attributes
|
||||
|
||||
// Color
|
||||
RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor)
|
||||
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor)
|
||||
RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat)
|
||||
// Font
|
||||
RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString)
|
||||
RCT_REMAP_SHADOW_PROPERTY(fontSize, textAttributes.fontSize, CGFloat)
|
||||
RCT_REMAP_SHADOW_PROPERTY(fontWeight, textAttributes.fontWeight, NSString)
|
||||
RCT_REMAP_SHADOW_PROPERTY(fontStyle, textAttributes.fontStyle, NSString)
|
||||
RCT_REMAP_SHADOW_PROPERTY(fontVariant, textAttributes.fontVariant, NSArray)
|
||||
RCT_REMAP_SHADOW_PROPERTY(allowFontScaling, textAttributes.allowFontScaling, BOOL)
|
||||
RCT_REMAP_SHADOW_PROPERTY(maxFontSizeMultiplier, textAttributes.maxFontSizeMultiplier, CGFloat)
|
||||
RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat)
|
||||
// Paragraph Styles
|
||||
RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment)
|
||||
RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection)
|
||||
// Decoration
|
||||
RCT_REMAP_SHADOW_PROPERTY(textDecorationColor, textAttributes.textDecorationColor, UIColor)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textDecorationStyle, textAttributes.textDecorationStyle, NSUnderlineStyle)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textDecorationLine, textAttributes.textDecorationLine, RCTTextDecorationLineType)
|
||||
// Shadow
|
||||
RCT_REMAP_SHADOW_PROPERTY(textShadowOffset, textAttributes.textShadowOffset, CGSize)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textShadowRadius, textAttributes.textShadowRadius, CGFloat)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textShadowColor, textAttributes.textShadowColor, UIColor)
|
||||
// Special
|
||||
RCT_REMAP_SHADOW_PROPERTY(isHighlighted, textAttributes.isHighlighted, BOOL)
|
||||
RCT_REMAP_SHADOW_PROPERTY(textTransform, textAttributes.textTransform, RCTTextTransform)
|
||||
|
||||
@end
|
22
node_modules/react-native/Libraries/Text/RCTConvert+Text.h
generated
vendored
Normal file
22
node_modules/react-native/Libraries/Text/RCTConvert+Text.h
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#import "RCTTextTransform.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTConvert (Text)
|
||||
|
||||
+ (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json;
|
||||
+ (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json;
|
||||
+ (RCTTextTransform)RCTTextTransform:(nullable id)json;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
35
node_modules/react-native/Libraries/Text/RCTConvert+Text.m
generated
vendored
Normal file
35
node_modules/react-native/Libraries/Text/RCTConvert+Text.m
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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+Text.h>
|
||||
|
||||
@implementation RCTConvert (Text)
|
||||
|
||||
+ (UITextAutocorrectionType)UITextAutocorrectionType:(id)json
|
||||
{
|
||||
return
|
||||
json == nil ? UITextAutocorrectionTypeDefault :
|
||||
[RCTConvert BOOL:json] ? UITextAutocorrectionTypeYes :
|
||||
UITextAutocorrectionTypeNo;
|
||||
}
|
||||
|
||||
+ (UITextSpellCheckingType)UITextSpellCheckingType:(id)json
|
||||
{
|
||||
return
|
||||
json == nil ? UITextSpellCheckingTypeDefault :
|
||||
[RCTConvert BOOL:json] ? UITextSpellCheckingTypeYes :
|
||||
UITextSpellCheckingTypeNo;
|
||||
}
|
||||
|
||||
RCT_ENUM_CONVERTER(RCTTextTransform, (@{
|
||||
@"none": @(RCTTextTransformNone),
|
||||
@"capitalize": @(RCTTextTransformCapitalize),
|
||||
@"uppercase": @(RCTTextTransformUppercase),
|
||||
@"lowercase": @(RCTTextTransformLowercase),
|
||||
}), RCTTextTransformUndefined, integerValue)
|
||||
|
||||
@end
|
97
node_modules/react-native/Libraries/Text/RCTTextAttributes.h
generated
vendored
Normal file
97
node_modules/react-native/Libraries/Text/RCTTextAttributes.h
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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/RCTTextDecorationLineType.h>
|
||||
|
||||
#import "RCTTextTransform.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString *const RCTTextAttributesIsHighlightedAttributeName;
|
||||
extern NSString *const RCTTextAttributesTagAttributeName;
|
||||
|
||||
/**
|
||||
* Represents knowledge about all supported *text* attributes
|
||||
* assigned to some text component such as <Text>, <VirtualText>,
|
||||
* and <TextInput>.
|
||||
*/
|
||||
@interface RCTTextAttributes : NSObject <NSCopying>
|
||||
|
||||
// Color
|
||||
@property (nonatomic, strong, nullable) UIColor *foregroundColor;
|
||||
@property (nonatomic, strong, nullable) UIColor *backgroundColor;
|
||||
@property (nonatomic, assign) CGFloat opacity;
|
||||
// Font
|
||||
@property (nonatomic, copy, nullable) NSString *fontFamily;
|
||||
@property (nonatomic, assign) CGFloat fontSize;
|
||||
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
|
||||
@property (nonatomic, assign) CGFloat maxFontSizeMultiplier;
|
||||
@property (nonatomic, copy, nullable) NSString *fontWeight;
|
||||
@property (nonatomic, copy, nullable) NSString *fontStyle;
|
||||
@property (nonatomic, copy, nullable) NSArray<NSString *> *fontVariant;
|
||||
@property (nonatomic, assign) BOOL allowFontScaling;
|
||||
@property (nonatomic, assign) CGFloat letterSpacing;
|
||||
// Paragraph Styles
|
||||
@property (nonatomic, assign) CGFloat lineHeight;
|
||||
@property (nonatomic, assign) NSTextAlignment alignment;
|
||||
@property (nonatomic, assign) NSWritingDirection baseWritingDirection;
|
||||
// Decoration
|
||||
@property (nonatomic, strong, nullable) UIColor *textDecorationColor;
|
||||
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
|
||||
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
|
||||
// Shadow
|
||||
@property (nonatomic, assign) CGSize textShadowOffset;
|
||||
@property (nonatomic, assign) CGFloat textShadowRadius;
|
||||
@property (nonatomic, strong, nullable) UIColor *textShadowColor;
|
||||
// Special
|
||||
@property (nonatomic, assign) BOOL isHighlighted;
|
||||
@property (nonatomic, strong, nullable) NSNumber *tag;
|
||||
@property (nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection;
|
||||
@property (nonatomic, assign) RCTTextTransform textTransform;
|
||||
|
||||
#pragma mark - Inheritance
|
||||
|
||||
- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes;
|
||||
|
||||
#pragma mark - Adapters
|
||||
|
||||
/**
|
||||
* Text attributes in NSAttributedString terms.
|
||||
*/
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes;
|
||||
|
||||
/**
|
||||
* Constructed paragraph style.
|
||||
*/
|
||||
- (NSParagraphStyle *_Nullable)effectiveParagraphStyle;
|
||||
|
||||
/**
|
||||
* Constructed font.
|
||||
*/
|
||||
- (UIFont *)effectiveFont;
|
||||
|
||||
/**
|
||||
* Font size multiplier reflects `allowFontScaling`, `fontSizeMultiplier`, and `maxFontSizeMultiplier`.
|
||||
*/
|
||||
- (CGFloat)effectiveFontSizeMultiplier;
|
||||
|
||||
/**
|
||||
* Foreground and background colors with opacity and right defaults.
|
||||
*/
|
||||
- (UIColor *)effectiveForegroundColor;
|
||||
- (UIColor *)effectiveBackgroundColor;
|
||||
|
||||
/**
|
||||
* Text transformed per 'none', 'uppercase', 'lowercase', 'capitalize'
|
||||
*/
|
||||
- (NSString *)applyTextAttributesToText:(NSString *)text;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
312
node_modules/react-native/Libraries/Text/RCTTextAttributes.m
generated
vendored
Normal file
312
node_modules/react-native/Libraries/Text/RCTTextAttributes.m
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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/RCTTextAttributes.h>
|
||||
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
NSString *const RCTTextAttributesIsHighlightedAttributeName = @"RCTTextAttributesIsHighlightedAttributeName";
|
||||
NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttributeName";
|
||||
|
||||
@implementation RCTTextAttributes
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_fontSize = NAN;
|
||||
_letterSpacing = NAN;
|
||||
_lineHeight = NAN;
|
||||
_textDecorationStyle = NSUnderlineStyleSingle;
|
||||
_fontSizeMultiplier = NAN;
|
||||
_maxFontSizeMultiplier = NAN;
|
||||
_alignment = NSTextAlignmentNatural;
|
||||
_baseWritingDirection = NSWritingDirectionNatural;
|
||||
_textShadowRadius = NAN;
|
||||
_opacity = NAN;
|
||||
_textTransform = RCTTextTransformUndefined;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
|
||||
{
|
||||
// Note: All lines marked with `*` does not use explicit/correct rules to compare old and new values because
|
||||
// their types do not have special designated value representing undefined/unspecified/inherit meaning.
|
||||
// We will address this in the future.
|
||||
|
||||
// Color
|
||||
_foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor;
|
||||
_backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor;
|
||||
_opacity = !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity;
|
||||
|
||||
// Font
|
||||
_fontFamily = textAttributes->_fontFamily ?: _fontFamily;
|
||||
_fontSize = !isnan(textAttributes->_fontSize) ? textAttributes->_fontSize : _fontSize;
|
||||
_fontSizeMultiplier = !isnan(textAttributes->_fontSizeMultiplier) ? textAttributes->_fontSizeMultiplier : _fontSizeMultiplier;
|
||||
_maxFontSizeMultiplier = !isnan(textAttributes->_maxFontSizeMultiplier) ? textAttributes->_maxFontSizeMultiplier : _maxFontSizeMultiplier;
|
||||
_fontWeight = textAttributes->_fontWeight ?: _fontWeight;
|
||||
_fontStyle = textAttributes->_fontStyle ?: _fontStyle;
|
||||
_fontVariant = textAttributes->_fontVariant ?: _fontVariant;
|
||||
_allowFontScaling = textAttributes->_allowFontScaling || _allowFontScaling; // *
|
||||
_letterSpacing = !isnan(textAttributes->_letterSpacing) ? textAttributes->_letterSpacing : _letterSpacing;
|
||||
|
||||
// Paragraph Styles
|
||||
_lineHeight = !isnan(textAttributes->_lineHeight) ? textAttributes->_lineHeight : _lineHeight;
|
||||
_alignment = textAttributes->_alignment != NSTextAlignmentNatural ? textAttributes->_alignment : _alignment; // *
|
||||
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural ? textAttributes->_baseWritingDirection : _baseWritingDirection; // *
|
||||
|
||||
// Decoration
|
||||
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
|
||||
_textDecorationStyle = textAttributes->_textDecorationStyle != NSUnderlineStyleSingle ? textAttributes->_textDecorationStyle : _textDecorationStyle; // *
|
||||
_textDecorationLine = textAttributes->_textDecorationLine != RCTTextDecorationLineTypeNone ? textAttributes->_textDecorationLine : _textDecorationLine; // *
|
||||
|
||||
// Shadow
|
||||
_textShadowOffset = !CGSizeEqualToSize(textAttributes->_textShadowOffset, CGSizeZero) ? textAttributes->_textShadowOffset : _textShadowOffset; // *
|
||||
_textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius;
|
||||
_textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor;
|
||||
|
||||
// Special
|
||||
_isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // *
|
||||
_tag = textAttributes->_tag ?: _tag;
|
||||
_layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight ? textAttributes->_layoutDirection : _layoutDirection;
|
||||
_textTransform = textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform;
|
||||
}
|
||||
|
||||
- (NSParagraphStyle *)effectiveParagraphStyle
|
||||
{
|
||||
// Paragraph Style
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
BOOL isParagraphStyleUsed = NO;
|
||||
if (_alignment != NSTextAlignmentNatural) {
|
||||
NSTextAlignment alignment = _alignment;
|
||||
if (_layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
|
||||
if (alignment == NSTextAlignmentRight) {
|
||||
alignment = NSTextAlignmentLeft;
|
||||
} else if (alignment == NSTextAlignmentLeft) {
|
||||
alignment = NSTextAlignmentRight;
|
||||
}
|
||||
}
|
||||
|
||||
paragraphStyle.alignment = alignment;
|
||||
isParagraphStyleUsed = YES;
|
||||
}
|
||||
|
||||
if (_baseWritingDirection != NSWritingDirectionNatural) {
|
||||
paragraphStyle.baseWritingDirection = _baseWritingDirection;
|
||||
isParagraphStyleUsed = YES;
|
||||
}
|
||||
|
||||
if (!isnan(_lineHeight)) {
|
||||
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
|
||||
paragraphStyle.minimumLineHeight = lineHeight;
|
||||
paragraphStyle.maximumLineHeight = lineHeight;
|
||||
isParagraphStyleUsed = YES;
|
||||
}
|
||||
|
||||
if (isParagraphStyleUsed) {
|
||||
return [paragraphStyle copy];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSAttributedStringKey, id> *)effectiveTextAttributes
|
||||
{
|
||||
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
|
||||
[NSMutableDictionary dictionaryWithCapacity:10];
|
||||
|
||||
// Font
|
||||
UIFont *font = self.effectiveFont;
|
||||
if (font) {
|
||||
attributes[NSFontAttributeName] = font;
|
||||
}
|
||||
|
||||
// Colors
|
||||
UIColor *effectiveForegroundColor = self.effectiveForegroundColor;
|
||||
|
||||
if (_foregroundColor || !isnan(_opacity)) {
|
||||
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
|
||||
}
|
||||
|
||||
if (_backgroundColor || !isnan(_opacity)) {
|
||||
attributes[NSBackgroundColorAttributeName] = self.effectiveBackgroundColor;
|
||||
}
|
||||
|
||||
// Kerning
|
||||
if (!isnan(_letterSpacing)) {
|
||||
attributes[NSKernAttributeName] = @(_letterSpacing);
|
||||
}
|
||||
|
||||
// Paragraph Style
|
||||
NSParagraphStyle *paragraphStyle = [self effectiveParagraphStyle];
|
||||
if (paragraphStyle) {
|
||||
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
|
||||
}
|
||||
|
||||
// Decoration
|
||||
BOOL isTextDecorationEnabled = NO;
|
||||
if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
|
||||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
|
||||
isTextDecorationEnabled = YES;
|
||||
attributes[NSUnderlineStyleAttributeName] = @(_textDecorationStyle);
|
||||
}
|
||||
|
||||
if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
|
||||
_textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
|
||||
isTextDecorationEnabled = YES;
|
||||
attributes[NSStrikethroughStyleAttributeName] = @(_textDecorationStyle);
|
||||
}
|
||||
|
||||
if (_textDecorationColor || isTextDecorationEnabled) {
|
||||
attributes[NSStrikethroughColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
|
||||
attributes[NSUnderlineColorAttributeName] = _textDecorationColor ?: effectiveForegroundColor;
|
||||
}
|
||||
|
||||
// Shadow
|
||||
if (!isnan(_textShadowRadius)) {
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowOffset = _textShadowOffset;
|
||||
shadow.shadowBlurRadius = _textShadowRadius;
|
||||
shadow.shadowColor = _textShadowColor;
|
||||
attributes[NSShadowAttributeName] = shadow;
|
||||
}
|
||||
|
||||
// Special
|
||||
if (_isHighlighted) {
|
||||
attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES;
|
||||
}
|
||||
|
||||
if (_tag) {
|
||||
attributes[RCTTextAttributesTagAttributeName] = _tag;
|
||||
}
|
||||
|
||||
return [attributes copy];
|
||||
}
|
||||
|
||||
- (UIFont *)effectiveFont
|
||||
{
|
||||
// FIXME: RCTFont has thread-safety issues and must be rewritten.
|
||||
return [RCTFont updateFont:nil
|
||||
withFamily:_fontFamily
|
||||
size:@(isnan(_fontSize) ? 0 : _fontSize)
|
||||
weight:_fontWeight
|
||||
style:_fontStyle
|
||||
variant:_fontVariant
|
||||
scaleMultiplier:self.effectiveFontSizeMultiplier];
|
||||
}
|
||||
|
||||
- (CGFloat)effectiveFontSizeMultiplier
|
||||
{
|
||||
bool fontScalingEnabled = !RCTHasFontHandlerSet() && _allowFontScaling;
|
||||
|
||||
if (fontScalingEnabled) {
|
||||
CGFloat fontSizeMultiplier = !isnan(_fontSizeMultiplier) ? _fontSizeMultiplier : 1.0;
|
||||
CGFloat maxFontSizeMultiplier = !isnan(_maxFontSizeMultiplier) ? _maxFontSizeMultiplier : 0.0;
|
||||
return maxFontSizeMultiplier >= 1.0 ? fminf(maxFontSizeMultiplier, fontSizeMultiplier) : fontSizeMultiplier;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor *)effectiveForegroundColor
|
||||
{
|
||||
UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor];
|
||||
|
||||
if (!isnan(_opacity)) {
|
||||
effectiveForegroundColor = [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity];
|
||||
}
|
||||
|
||||
return effectiveForegroundColor;
|
||||
}
|
||||
|
||||
- (UIColor *)effectiveBackgroundColor
|
||||
{
|
||||
UIColor *effectiveBackgroundColor = _backgroundColor;// ?: [[UIColor whiteColor] colorWithAlphaComponent:0];
|
||||
|
||||
if (effectiveBackgroundColor && !isnan(_opacity)) {
|
||||
effectiveBackgroundColor = [effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * _opacity];
|
||||
}
|
||||
|
||||
return effectiveBackgroundColor ?: [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (NSString *)applyTextAttributesToText:(NSString *)text
|
||||
{
|
||||
switch (_textTransform) {
|
||||
case RCTTextTransformUndefined:
|
||||
case RCTTextTransformNone:
|
||||
return text;
|
||||
case RCTTextTransformLowercase:
|
||||
return [text lowercaseString];
|
||||
case RCTTextTransformUppercase:
|
||||
return [text uppercaseString];
|
||||
case RCTTextTransformCapitalize:
|
||||
return [text capitalizedString];
|
||||
}
|
||||
}
|
||||
|
||||
- (RCTTextAttributes *)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
RCTTextAttributes *textAttributes = [RCTTextAttributes new];
|
||||
[textAttributes applyTextAttributes:self];
|
||||
return textAttributes;
|
||||
}
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
- (BOOL)isEqual:(RCTTextAttributes *)textAttributes
|
||||
{
|
||||
if (!textAttributes) {
|
||||
return NO;
|
||||
}
|
||||
if (self == textAttributes) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#define RCTTextAttributesCompareFloats(a) ((a == textAttributes->a) || (isnan(a) && isnan(textAttributes->a)))
|
||||
#define RCTTextAttributesCompareSize(a) CGSizeEqualToSize(a, textAttributes->a)
|
||||
#define RCTTextAttributesCompareObjects(a) ((a == textAttributes->a) || [a isEqual:textAttributes->a])
|
||||
#define RCTTextAttributesCompareStrings(a) ((a == textAttributes->a) || [a isEqualToString:textAttributes->a])
|
||||
#define RCTTextAttributesCompareOthers(a) (a == textAttributes->a)
|
||||
|
||||
return
|
||||
RCTTextAttributesCompareObjects(_foregroundColor) &&
|
||||
RCTTextAttributesCompareObjects(_backgroundColor) &&
|
||||
RCTTextAttributesCompareFloats(_opacity) &&
|
||||
// Font
|
||||
RCTTextAttributesCompareObjects(_fontFamily) &&
|
||||
RCTTextAttributesCompareFloats(_fontSize) &&
|
||||
RCTTextAttributesCompareFloats(_fontSizeMultiplier) &&
|
||||
RCTTextAttributesCompareFloats(_maxFontSizeMultiplier) &&
|
||||
RCTTextAttributesCompareStrings(_fontWeight) &&
|
||||
RCTTextAttributesCompareObjects(_fontStyle) &&
|
||||
RCTTextAttributesCompareObjects(_fontVariant) &&
|
||||
RCTTextAttributesCompareOthers(_allowFontScaling) &&
|
||||
RCTTextAttributesCompareFloats(_letterSpacing) &&
|
||||
// Paragraph Styles
|
||||
RCTTextAttributesCompareFloats(_lineHeight) &&
|
||||
RCTTextAttributesCompareFloats(_alignment) &&
|
||||
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
|
||||
// Decoration
|
||||
RCTTextAttributesCompareObjects(_textDecorationColor) &&
|
||||
RCTTextAttributesCompareOthers(_textDecorationStyle) &&
|
||||
RCTTextAttributesCompareOthers(_textDecorationLine) &&
|
||||
// Shadow
|
||||
RCTTextAttributesCompareSize(_textShadowOffset) &&
|
||||
RCTTextAttributesCompareFloats(_textShadowRadius) &&
|
||||
RCTTextAttributesCompareObjects(_textShadowColor) &&
|
||||
// Special
|
||||
RCTTextAttributesCompareOthers(_isHighlighted) &&
|
||||
RCTTextAttributesCompareObjects(_tag) &&
|
||||
RCTTextAttributesCompareOthers(_layoutDirection) &&
|
||||
RCTTextAttributesCompareOthers(_textTransform);
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/RCTTextTransform.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/RCTTextTransform.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 <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTextTransform) {
|
||||
RCTTextTransformUndefined = 0,
|
||||
RCTTextTransformNone,
|
||||
RCTTextTransformCapitalize,
|
||||
RCTTextTransformUppercase,
|
||||
RCTTextTransformLowercase,
|
||||
};
|
18
node_modules/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h
generated
vendored
Normal file
18
node_modules/react-native/Libraries/Text/RawText/RCTRawTextShadowView.h
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTRawTextShadowView : RCTShadowView
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *text;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
33
node_modules/react-native/Libraries/Text/RawText/RCTRawTextShadowView.m
generated
vendored
Normal file
33
node_modules/react-native/Libraries/Text/RawText/RCTRawTextShadowView.m
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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/RCTRawTextShadowView.h>
|
||||
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
|
||||
@implementation RCTRawTextShadowView
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
if (_text != text && ![_text isEqualToString:text]) {
|
||||
_text = [text copy];
|
||||
[self dirtyLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dirtyLayout
|
||||
{
|
||||
[self.superview dirtyLayout];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *superDescription = super.description;
|
||||
return [[superDescription substringToIndex:superDescription.length - 1] stringByAppendingFormat:@"; text: %@>", self.text];
|
||||
}
|
||||
|
||||
@end
|
16
node_modules/react-native/Libraries/Text/RawText/RCTRawTextViewManager.h
generated
vendored
Normal file
16
node_modules/react-native/Libraries/Text/RawText/RCTRawTextViewManager.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 <React/RCTViewManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTRawTextViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
28
node_modules/react-native/Libraries/Text/RawText/RCTRawTextViewManager.m
generated
vendored
Normal file
28
node_modules/react-native/Libraries/Text/RawText/RCTRawTextViewManager.m
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/RCTRawTextViewManager.h>
|
||||
|
||||
#import <React/RCTRawTextShadowView.h>
|
||||
|
||||
@implementation RCTRawTextViewManager
|
||||
|
||||
RCT_EXPORT_MODULE(RCTRawText)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [UIView new];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [RCTRawTextShadowView new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
||||
|
||||
@end
|
34
node_modules/react-native/Libraries/Text/React-RCTText.podspec
generated
vendored
Normal file
34
node_modules/react-native/Libraries/Text/React-RCTText.podspec
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.
|
||||
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-RCTText"
|
||||
s.version = version
|
||||
s.summary = "A React component for displaying text."
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.documentation_url = "https://reactnative.dev/docs/text"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "10.0", :tvos => "10.0" }
|
||||
s.source = source
|
||||
s.source_files = "**/*.{h,m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "RCTText"
|
||||
|
||||
s.dependency "React-Core/RCTTextHeaders", version
|
||||
end
|
302
node_modules/react-native/Libraries/Text/Text.js
generated
vendored
Normal file
302
node_modules/react-native/Libraries/Text/Text.js
generated
vendored
Normal file
@ -0,0 +1,302 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const DeprecatedTextPropTypes = require('../DeprecatedPropTypes/DeprecatedTextPropTypes');
|
||||
const React = require('react');
|
||||
const ReactNativeViewAttributes = require('../Components/View/ReactNativeViewAttributes');
|
||||
const TextAncestor = require('./TextAncestor');
|
||||
const Touchable = require('../Components/Touchable/Touchable');
|
||||
const UIManager = require('../ReactNative/UIManager');
|
||||
|
||||
const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');
|
||||
const nullthrows = require('nullthrows');
|
||||
const processColor = require('../StyleSheet/processColor');
|
||||
|
||||
import type {PressEvent} from '../Types/CoreEventTypes';
|
||||
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
|
||||
import type {PressRetentionOffset, TextProps} from './TextProps';
|
||||
|
||||
type ResponseHandlers = $ReadOnly<{|
|
||||
onStartShouldSetResponder: () => boolean,
|
||||
onResponderGrant: (event: PressEvent, dispatchID: string) => void,
|
||||
onResponderMove: (event: PressEvent) => void,
|
||||
onResponderRelease: (event: PressEvent) => void,
|
||||
onResponderTerminate: (event: PressEvent) => void,
|
||||
onResponderTerminationRequest: () => boolean,
|
||||
|}>;
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...TextProps,
|
||||
forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>,
|
||||
|}>;
|
||||
|
||||
type State = {|
|
||||
touchable: {|
|
||||
touchState: ?string,
|
||||
responderID: ?number,
|
||||
|},
|
||||
isHighlighted: boolean,
|
||||
createResponderHandlers: () => ResponseHandlers,
|
||||
responseHandlers: ?ResponseHandlers,
|
||||
|};
|
||||
|
||||
const PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
const viewConfig = {
|
||||
validAttributes: {
|
||||
...ReactNativeViewAttributes.UIView,
|
||||
isHighlighted: true,
|
||||
numberOfLines: true,
|
||||
ellipsizeMode: true,
|
||||
allowFontScaling: true,
|
||||
maxFontSizeMultiplier: true,
|
||||
disabled: true,
|
||||
selectable: true,
|
||||
selectionColor: true,
|
||||
adjustsFontSizeToFit: true,
|
||||
minimumFontScale: true,
|
||||
textBreakStrategy: true,
|
||||
onTextLayout: true,
|
||||
onInlineViewLayout: true,
|
||||
dataDetectorType: true,
|
||||
},
|
||||
directEventTypes: {
|
||||
topTextLayout: {
|
||||
registrationName: 'onTextLayout',
|
||||
},
|
||||
topInlineViewLayout: {
|
||||
registrationName: 'onInlineViewLayout',
|
||||
},
|
||||
},
|
||||
uiViewClassName: 'RCTText',
|
||||
};
|
||||
|
||||
/**
|
||||
* A React component for displaying text.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html
|
||||
*/
|
||||
class TouchableText extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
accessible: true,
|
||||
allowFontScaling: true,
|
||||
ellipsizeMode: 'tail',
|
||||
};
|
||||
|
||||
touchableGetPressRectOffset: ?() => PressRetentionOffset;
|
||||
touchableHandleActivePressIn: ?() => void;
|
||||
touchableHandleActivePressOut: ?() => void;
|
||||
touchableHandleLongPress: ?(event: PressEvent) => void;
|
||||
touchableHandlePress: ?(event: PressEvent) => void;
|
||||
touchableHandleResponderGrant: ?(
|
||||
event: PressEvent,
|
||||
dispatchID: string,
|
||||
) => void;
|
||||
touchableHandleResponderMove: ?(event: PressEvent) => void;
|
||||
touchableHandleResponderRelease: ?(event: PressEvent) => void;
|
||||
touchableHandleResponderTerminate: ?(event: PressEvent) => void;
|
||||
touchableHandleResponderTerminationRequest: ?() => boolean;
|
||||
|
||||
state = {
|
||||
...Touchable.Mixin.touchableGetInitialState(),
|
||||
isHighlighted: false,
|
||||
createResponderHandlers: this._createResponseHandlers.bind(this),
|
||||
responseHandlers: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
nextProps: Props,
|
||||
prevState: State,
|
||||
): $Shape<State> | null {
|
||||
return prevState.responseHandlers == null && isTouchable(nextProps)
|
||||
? {
|
||||
responseHandlers: prevState.createResponderHandlers(),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
static viewConfig = viewConfig;
|
||||
|
||||
render(): React.Node {
|
||||
let props = this.props;
|
||||
if (isTouchable(props)) {
|
||||
props = {
|
||||
...props,
|
||||
...this.state.responseHandlers,
|
||||
isHighlighted: this.state.isHighlighted,
|
||||
};
|
||||
}
|
||||
if (props.selectionColor != null) {
|
||||
props = {
|
||||
...props,
|
||||
selectionColor: processColor(props.selectionColor),
|
||||
};
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) {
|
||||
props = {
|
||||
...props,
|
||||
style: [props.style, {color: 'magenta'}],
|
||||
};
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TextAncestor.Consumer>
|
||||
{hasTextAncestor =>
|
||||
hasTextAncestor ? (
|
||||
<RCTVirtualText {...props} ref={props.forwardedRef} />
|
||||
) : (
|
||||
<TextAncestor.Provider value={true}>
|
||||
<RCTText {...props} ref={props.forwardedRef} />
|
||||
</TextAncestor.Provider>
|
||||
)
|
||||
}
|
||||
</TextAncestor.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
_createResponseHandlers(): ResponseHandlers {
|
||||
return {
|
||||
onStartShouldSetResponder: (): boolean => {
|
||||
const {onStartShouldSetResponder} = this.props;
|
||||
const shouldSetResponder =
|
||||
(onStartShouldSetResponder == null
|
||||
? false
|
||||
: onStartShouldSetResponder()) || isTouchable(this.props);
|
||||
|
||||
if (shouldSetResponder) {
|
||||
this._attachTouchHandlers();
|
||||
}
|
||||
return shouldSetResponder;
|
||||
},
|
||||
onResponderGrant: (event: PressEvent, dispatchID: string): void => {
|
||||
nullthrows(this.touchableHandleResponderGrant)(event, dispatchID);
|
||||
if (this.props.onResponderGrant != null) {
|
||||
this.props.onResponderGrant.call(this, event, dispatchID);
|
||||
}
|
||||
},
|
||||
onResponderMove: (event: PressEvent): void => {
|
||||
nullthrows(this.touchableHandleResponderMove)(event);
|
||||
if (this.props.onResponderMove != null) {
|
||||
this.props.onResponderMove.call(this, event);
|
||||
}
|
||||
},
|
||||
onResponderRelease: (event: PressEvent): void => {
|
||||
nullthrows(this.touchableHandleResponderRelease)(event);
|
||||
if (this.props.onResponderRelease != null) {
|
||||
this.props.onResponderRelease.call(this, event);
|
||||
}
|
||||
},
|
||||
onResponderTerminate: (event: PressEvent): void => {
|
||||
nullthrows(this.touchableHandleResponderTerminate)(event);
|
||||
if (this.props.onResponderTerminate != null) {
|
||||
this.props.onResponderTerminate.call(this, event);
|
||||
}
|
||||
},
|
||||
onResponderTerminationRequest: (): boolean => {
|
||||
const {onResponderTerminationRequest} = this.props;
|
||||
if (!nullthrows(this.touchableHandleResponderTerminationRequest)()) {
|
||||
return false;
|
||||
}
|
||||
if (onResponderTerminationRequest == null) {
|
||||
return true;
|
||||
}
|
||||
return onResponderTerminationRequest();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily attaches Touchable.Mixin handlers.
|
||||
*/
|
||||
_attachTouchHandlers(): void {
|
||||
if (this.touchableGetPressRectOffset != null) {
|
||||
return;
|
||||
}
|
||||
for (const key in Touchable.Mixin) {
|
||||
if (typeof Touchable.Mixin[key] === 'function') {
|
||||
(this: any)[key] = Touchable.Mixin[key].bind(this);
|
||||
}
|
||||
}
|
||||
this.touchableHandleActivePressIn = (): void => {
|
||||
if (!this.props.suppressHighlighting && isTouchable(this.props)) {
|
||||
this.setState({isHighlighted: true});
|
||||
}
|
||||
};
|
||||
this.touchableHandleActivePressOut = (): void => {
|
||||
if (!this.props.suppressHighlighting && isTouchable(this.props)) {
|
||||
this.setState({isHighlighted: false});
|
||||
}
|
||||
};
|
||||
this.touchableHandlePress = (event: PressEvent): void => {
|
||||
if (this.props.onPress != null) {
|
||||
this.props.onPress(event);
|
||||
}
|
||||
};
|
||||
this.touchableHandleLongPress = (event: PressEvent): void => {
|
||||
if (this.props.onLongPress != null) {
|
||||
this.props.onLongPress(event);
|
||||
}
|
||||
};
|
||||
this.touchableGetPressRectOffset = (): PressRetentionOffset =>
|
||||
this.props.pressRetentionOffset == null
|
||||
? PRESS_RECT_OFFSET
|
||||
: this.props.pressRetentionOffset;
|
||||
}
|
||||
}
|
||||
|
||||
const isTouchable = (props: Props): boolean =>
|
||||
props.onPress != null ||
|
||||
props.onLongPress != null ||
|
||||
props.onStartShouldSetResponder != null;
|
||||
|
||||
const RCTText = createReactNativeComponentClass(
|
||||
viewConfig.uiViewClassName,
|
||||
() => viewConfig,
|
||||
);
|
||||
|
||||
const RCTVirtualText =
|
||||
UIManager.getViewManagerConfig('RCTVirtualText') == null
|
||||
? RCTText
|
||||
: createReactNativeComponentClass('RCTVirtualText', () => ({
|
||||
validAttributes: {
|
||||
...ReactNativeViewAttributes.UIView,
|
||||
isHighlighted: true,
|
||||
maxFontSizeMultiplier: true,
|
||||
},
|
||||
uiViewClassName: 'RCTVirtualText',
|
||||
}));
|
||||
|
||||
const Text = (
|
||||
props: TextProps,
|
||||
forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>,
|
||||
) => {
|
||||
return <TouchableText {...props} forwardedRef={forwardedRef} />;
|
||||
};
|
||||
const TextToExport = React.forwardRef(Text);
|
||||
TextToExport.displayName = 'Text';
|
||||
|
||||
// TODO: Deprecate this.
|
||||
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an error
|
||||
* found when Flow v0.89 was deployed. To see the error, delete this comment
|
||||
* and run Flow. */
|
||||
TextToExport.propTypes = DeprecatedTextPropTypes;
|
||||
|
||||
type TextStatics = $ReadOnly<{|
|
||||
propTypes: typeof DeprecatedTextPropTypes,
|
||||
|}>;
|
||||
|
||||
module.exports = ((TextToExport: any): React.AbstractComponent<
|
||||
TextProps,
|
||||
React.ElementRef<HostComponent<TextProps>>,
|
||||
> &
|
||||
TextStatics);
|
20
node_modules/react-native/Libraries/Text/Text/NSTextStorage+FontScaling.h
generated
vendored
Normal file
20
node_modules/react-native/Libraries/Text/Text/NSTextStorage+FontScaling.h
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 <UIKit/UIKit.h>
|
||||
|
||||
@interface NSTextStorage (FontScaling)
|
||||
|
||||
- (void)scaleFontSizeToFitSize:(CGSize)size
|
||||
minimumFontSize:(CGFloat)minimumFontSize
|
||||
maximumFontSize:(CGFloat)maximumFontSize;
|
||||
|
||||
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
|
||||
minimumFontSize:(CGFloat)minimumFontSize
|
||||
maximumFontSize:(CGFloat)maximumFontSize;
|
||||
|
||||
@end
|
137
node_modules/react-native/Libraries/Text/Text/NSTextStorage+FontScaling.m
generated
vendored
Normal file
137
node_modules/react-native/Libraries/Text/Text/NSTextStorage+FontScaling.m
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "NSTextStorage+FontScaling.h"
|
||||
|
||||
typedef NS_OPTIONS(NSInteger, RCTTextSizeComparisonOptions) {
|
||||
RCTTextSizeComparisonSmaller = 1 << 0,
|
||||
RCTTextSizeComparisonLarger = 1 << 1,
|
||||
RCTTextSizeComparisonWithinRange = 1 << 2,
|
||||
};
|
||||
|
||||
@implementation NSTextStorage (FontScaling)
|
||||
|
||||
- (void)scaleFontSizeToFitSize:(CGSize)size
|
||||
minimumFontSize:(CGFloat)minimumFontSize
|
||||
maximumFontSize:(CGFloat)maximumFontSize
|
||||
{
|
||||
CGFloat bottomRatio = 1.0/128.0;
|
||||
CGFloat topRatio = 128.0;
|
||||
CGFloat ratio = 1.0;
|
||||
|
||||
NSAttributedString *originalAttributedString = [self copy];
|
||||
|
||||
CGFloat lastRatioWhichFits = 0.02;
|
||||
|
||||
while (true) {
|
||||
[self scaleFontSizeWithRatio:ratio
|
||||
minimumFontSize:minimumFontSize
|
||||
maximumFontSize:maximumFontSize];
|
||||
|
||||
RCTTextSizeComparisonOptions comparsion =
|
||||
[self compareToSize:size thresholdRatio:0.01];
|
||||
|
||||
if (
|
||||
(comparsion & RCTTextSizeComparisonWithinRange) &&
|
||||
(comparsion & RCTTextSizeComparisonSmaller)
|
||||
) {
|
||||
return;
|
||||
} else if (comparsion & RCTTextSizeComparisonSmaller) {
|
||||
bottomRatio = ratio;
|
||||
lastRatioWhichFits = ratio;
|
||||
} else {
|
||||
topRatio = ratio;
|
||||
}
|
||||
|
||||
ratio = (topRatio + bottomRatio) / 2.0;
|
||||
|
||||
CGFloat kRatioThreshold = 0.005;
|
||||
if (
|
||||
ABS(topRatio - bottomRatio) < kRatioThreshold ||
|
||||
ABS(topRatio - ratio) < kRatioThreshold ||
|
||||
ABS(bottomRatio - ratio) < kRatioThreshold
|
||||
) {
|
||||
[self replaceCharactersInRange:(NSRange){0, self.length}
|
||||
withAttributedString:originalAttributedString];
|
||||
|
||||
[self scaleFontSizeWithRatio:lastRatioWhichFits
|
||||
minimumFontSize:minimumFontSize
|
||||
maximumFontSize:maximumFontSize];
|
||||
return;
|
||||
}
|
||||
|
||||
[self replaceCharactersInRange:(NSRange){0, self.length}
|
||||
withAttributedString:originalAttributedString];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (RCTTextSizeComparisonOptions)compareToSize:(CGSize)size thresholdRatio:(CGFloat)thresholdRatio
|
||||
{
|
||||
NSLayoutManager *layoutManager = self.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
|
||||
// Does it fit the text container?
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.length - 1];
|
||||
|
||||
if (truncatedGlyphRange.location != NSNotFound) {
|
||||
return RCTTextSizeComparisonLarger;
|
||||
}
|
||||
|
||||
CGSize measuredSize = [layoutManager usedRectForTextContainer:textContainer].size;
|
||||
|
||||
// Does it fit the size?
|
||||
BOOL fitsSize =
|
||||
size.width >= measuredSize.width &&
|
||||
size.height >= measuredSize.height;
|
||||
|
||||
CGSize thresholdSize = (CGSize){
|
||||
size.width * thresholdRatio,
|
||||
size.height * thresholdRatio,
|
||||
};
|
||||
|
||||
RCTTextSizeComparisonOptions result = 0;
|
||||
|
||||
result |= (fitsSize) ? RCTTextSizeComparisonSmaller : RCTTextSizeComparisonLarger;
|
||||
|
||||
if (ABS(measuredSize.width - size.width) < thresholdSize.width) {
|
||||
result = result | RCTTextSizeComparisonWithinRange;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
|
||||
minimumFontSize:(CGFloat)minimumFontSize
|
||||
maximumFontSize:(CGFloat)maximumFontSize
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
[self enumerateAttribute:NSFontAttributeName
|
||||
inRange:(NSRange){0, self.length}
|
||||
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
usingBlock:
|
||||
^(UIFont *_Nullable font, NSRange range, BOOL *_Nonnull stop) {
|
||||
if (!font) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat fontSize = MAX(MIN(font.pointSize * ratio, maximumFontSize), minimumFontSize);
|
||||
|
||||
[self addAttribute:NSFontAttributeName
|
||||
value:[font fontWithSize:fontSize]
|
||||
range:range];
|
||||
}
|
||||
];
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
@end
|
28
node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.h
generated
vendored
Normal file
28
node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.h
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#import "RCTBaseTextShadowView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTTextShadowView : RCTBaseTextShadowView
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@property (nonatomic, assign) NSInteger maximumNumberOfLines;
|
||||
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
|
||||
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
|
||||
@property (nonatomic, assign) CGFloat minimumFontScale;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onTextLayout;
|
||||
|
||||
- (void)uiManagerWillPerformMounting;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
439
node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m
generated
vendored
Normal file
439
node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m
generated
vendored
Normal file
@ -0,0 +1,439 @@
|
||||
/*
|
||||
* 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/RCTTextShadowView.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <yoga/Yoga.h>
|
||||
|
||||
#import "NSTextStorage+FontScaling.h"
|
||||
#import <React/RCTTextView.h>
|
||||
|
||||
@implementation RCTTextShadowView
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
BOOL _needsUpdateView;
|
||||
NSMapTable<id, NSTextStorage *> *_cachedTextStorages;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
_cachedTextStorages = [NSMapTable strongToStrongObjectsMapTable];
|
||||
_needsUpdateView = YES;
|
||||
YGNodeSetMeasureFunc(self.yogaNode, RCTTextShadowViewMeasure);
|
||||
YGNodeSetBaselineFunc(self.yogaNode, RCTTextShadowViewBaseline);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
|
||||
// When applying a semi-transparent background color to Text component
|
||||
// we must set the root text nodes text attribute background color to nil
|
||||
// because the background color is drawn on the RCTTextView itself, as well
|
||||
// as on the glphy background draw step. By setting this to nil, we allow
|
||||
// the RCTTextView backgroundColor to be used, without affecting nested Text
|
||||
// components.
|
||||
self.textAttributes.backgroundColor = nil;
|
||||
self.textAttributes.opacity = NAN;
|
||||
}
|
||||
|
||||
- (BOOL)isYogaLeafNode
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)dirtyLayout
|
||||
{
|
||||
[super dirtyLayout];
|
||||
YGNodeMarkDirty(self.yogaNode);
|
||||
[self invalidateCache];
|
||||
}
|
||||
|
||||
- (void)invalidateCache
|
||||
{
|
||||
[_cachedTextStorages removeAllObjects];
|
||||
_needsUpdateView = YES;
|
||||
}
|
||||
|
||||
#pragma mark - RCTUIManagerObserver
|
||||
|
||||
- (void)uiManagerWillPerformMounting
|
||||
{
|
||||
if (YGNodeIsDirty(self.yogaNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_needsUpdateView) {
|
||||
return;
|
||||
}
|
||||
_needsUpdateView = NO;
|
||||
|
||||
CGRect contentFrame = self.contentFrame;
|
||||
NSTextStorage *textStorage = [self textStorageAndLayoutManagerThatFitsSize:self.contentFrame.size
|
||||
exclusiveOwnership:YES];
|
||||
|
||||
NSNumber *tag = self.reactTag;
|
||||
NSMutableArray<NSNumber *> *descendantViewTags = [NSMutableArray new];
|
||||
[textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
|
||||
inRange:NSMakeRange(0, textStorage.length)
|
||||
options:0
|
||||
usingBlock:
|
||||
^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) {
|
||||
if (!shadowView) {
|
||||
return;
|
||||
}
|
||||
|
||||
[descendantViewTags addObject:shadowView.reactTag];
|
||||
}
|
||||
];
|
||||
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
RCTTextView *textView = (RCTTextView *)viewRegistry[tag];
|
||||
if (!textView) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<UIView *> *descendantViews =
|
||||
[NSMutableArray arrayWithCapacity:descendantViewTags.count];
|
||||
[descendantViewTags enumerateObjectsUsingBlock:^(NSNumber *_Nonnull descendantViewTag, NSUInteger index, BOOL *_Nonnull stop) {
|
||||
UIView *descendantView = viewRegistry[descendantViewTag];
|
||||
if (!descendantView) {
|
||||
return;
|
||||
}
|
||||
|
||||
[descendantViews addObject:descendantView];
|
||||
}];
|
||||
|
||||
// Removing all references to Shadow Views to avoid unnecessary retaining.
|
||||
[textStorage removeAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName range:NSMakeRange(0, textStorage.length)];
|
||||
|
||||
[textView setTextStorage:textStorage
|
||||
contentFrame:contentFrame
|
||||
descendantViews:descendantViews];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText
|
||||
{
|
||||
__block CGFloat maximumLineHeight = 0;
|
||||
|
||||
[attributedText enumerateAttribute:NSParagraphStyleAttributeName
|
||||
inRange:NSMakeRange(0, attributedText.length)
|
||||
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
usingBlock:
|
||||
^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
|
||||
if (!paragraphStyle) {
|
||||
return;
|
||||
}
|
||||
|
||||
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
|
||||
}
|
||||
];
|
||||
|
||||
if (maximumLineHeight == 0) {
|
||||
// `lineHeight` was not specified, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
__block CGFloat maximumFontLineHeight = 0;
|
||||
|
||||
[attributedText enumerateAttribute:NSFontAttributeName
|
||||
inRange:NSMakeRange(0, attributedText.length)
|
||||
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
||||
usingBlock:
|
||||
^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
||||
if (!font) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maximumFontLineHeight <= font.lineHeight) {
|
||||
maximumFontLineHeight = font.lineHeight;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (maximumLineHeight < maximumFontLineHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0;
|
||||
|
||||
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
||||
value:@(baseLineOffset)
|
||||
range:NSMakeRange(0, attributedText.length)];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size
|
||||
{
|
||||
static UIImage *placeholderImage;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
placeholderImage = [UIImage new];
|
||||
});
|
||||
|
||||
NSMutableAttributedString *attributedText =
|
||||
[[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]];
|
||||
|
||||
[attributedText beginEditing];
|
||||
|
||||
[attributedText enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
|
||||
inRange:NSMakeRange(0, attributedText.length)
|
||||
options:0
|
||||
usingBlock:
|
||||
^(RCTShadowView *shadowView, NSRange range, __unused BOOL *stop) {
|
||||
if (!shadowView) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize fittingSize = [shadowView sizeThatFitsMinimumSize:CGSizeZero
|
||||
maximumSize:size];
|
||||
NSTextAttachment *attachment = [NSTextAttachment new];
|
||||
attachment.bounds = (CGRect){CGPointZero, fittingSize};
|
||||
attachment.image = placeholderImage;
|
||||
[attributedText addAttribute:NSAttachmentAttributeName value:attachment range:range];
|
||||
}
|
||||
];
|
||||
|
||||
[attributedText endEditing];
|
||||
|
||||
return [attributedText copy];
|
||||
}
|
||||
|
||||
- (NSTextStorage *)textStorageAndLayoutManagerThatFitsSize:(CGSize)size
|
||||
exclusiveOwnership:(BOOL)exclusiveOwnership
|
||||
{
|
||||
NSValue *key = [NSValue valueWithCGSize:size];
|
||||
NSTextStorage *cachedTextStorage = [_cachedTextStorages objectForKey:key];
|
||||
|
||||
if (cachedTextStorage) {
|
||||
if (exclusiveOwnership) {
|
||||
[_cachedTextStorages removeObjectForKey:key];
|
||||
}
|
||||
|
||||
return cachedTextStorage;
|
||||
}
|
||||
|
||||
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
|
||||
|
||||
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
|
||||
textContainer.lineBreakMode =
|
||||
_maximumNumberOfLines > 0 ? _lineBreakMode : NSLineBreakByClipping;
|
||||
textContainer.maximumNumberOfLines = _maximumNumberOfLines;
|
||||
|
||||
NSLayoutManager *layoutManager = [NSLayoutManager new];
|
||||
layoutManager.usesFontLeading = NO;
|
||||
[layoutManager addTextContainer:textContainer];
|
||||
|
||||
NSTextStorage *textStorage =
|
||||
[[NSTextStorage alloc] initWithAttributedString:[self attributedTextWithMeasuredAttachmentsThatFitSize:size]];
|
||||
|
||||
[self postprocessAttributedText:textStorage];
|
||||
|
||||
[textStorage addLayoutManager:layoutManager];
|
||||
|
||||
if (_adjustsFontSizeToFit) {
|
||||
CGFloat minimumFontSize =
|
||||
MAX(_minimumFontScale * (self.textAttributes.effectiveFont.pointSize), 4.0);
|
||||
[textStorage scaleFontSizeToFitSize:size
|
||||
minimumFontSize:minimumFontSize
|
||||
maximumFontSize:self.textAttributes.effectiveFont.pointSize];
|
||||
}
|
||||
|
||||
if (!exclusiveOwnership) {
|
||||
[_cachedTextStorages setObject:textStorage forKey:key];
|
||||
}
|
||||
|
||||
return textStorage;
|
||||
}
|
||||
|
||||
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
|
||||
layoutContext:(RCTLayoutContext)layoutContext
|
||||
{
|
||||
// If the view got new `contentFrame`, we have to redraw it because
|
||||
// and sizes of embedded views may change.
|
||||
if (!CGRectEqualToRect(self.layoutMetrics.contentFrame, layoutMetrics.contentFrame)) {
|
||||
_needsUpdateView = YES;
|
||||
}
|
||||
|
||||
if (self.textAttributes.layoutDirection != layoutMetrics.layoutDirection) {
|
||||
self.textAttributes.layoutDirection = layoutMetrics.layoutDirection;
|
||||
[self invalidateCache];
|
||||
}
|
||||
|
||||
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
|
||||
}
|
||||
|
||||
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext
|
||||
{
|
||||
NSTextStorage *textStorage =
|
||||
[self textStorageAndLayoutManagerThatFitsSize:self.availableSize
|
||||
exclusiveOwnership:NO];
|
||||
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange
|
||||
actualGlyphRange:NULL];
|
||||
|
||||
[textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
|
||||
inRange:characterRange
|
||||
options:0
|
||||
usingBlock:
|
||||
^(RCTShadowView *shadowView, NSRange range, BOOL *stop) {
|
||||
if (!shadowView) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range
|
||||
inTextContainer:textContainer];
|
||||
|
||||
NSTextAttachment *attachment =
|
||||
[textStorage attribute:NSAttachmentAttributeName atIndex:range.location effectiveRange:nil];
|
||||
|
||||
CGSize attachmentSize = attachment.bounds.size;
|
||||
|
||||
UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
|
||||
|
||||
CGRect frame = {{
|
||||
RCTRoundPixelValue(glyphRect.origin.x),
|
||||
RCTRoundPixelValue(glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender)
|
||||
}, {
|
||||
RCTRoundPixelValue(attachmentSize.width),
|
||||
RCTRoundPixelValue(attachmentSize.height)
|
||||
}};
|
||||
|
||||
NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location];
|
||||
BOOL viewIsTruncated = NSIntersectionRange(range, truncatedGlyphRange).length != 0;
|
||||
|
||||
RCTLayoutContext localLayoutContext = layoutContext;
|
||||
localLayoutContext.absolutePosition.x += frame.origin.x;
|
||||
localLayoutContext.absolutePosition.y += frame.origin.y;
|
||||
|
||||
[shadowView layoutWithMinimumSize:frame.size
|
||||
maximumSize:frame.size
|
||||
layoutDirection:self.layoutMetrics.layoutDirection
|
||||
layoutContext:localLayoutContext];
|
||||
|
||||
RCTLayoutMetrics localLayoutMetrics = shadowView.layoutMetrics;
|
||||
localLayoutMetrics.frame.origin = frame.origin; // Reinforcing a proper frame origin for the Shadow View.
|
||||
if (viewIsTruncated) {
|
||||
localLayoutMetrics.displayType = RCTDisplayTypeNone;
|
||||
}
|
||||
[shadowView layoutWithMetrics:localLayoutMetrics layoutContext:localLayoutContext];
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
if (_onTextLayout) {
|
||||
NSMutableArray *lineData = [NSMutableArray new];
|
||||
[layoutManager
|
||||
enumerateLineFragmentsForGlyphRange:glyphRange
|
||||
usingBlock:^(CGRect overallRect, CGRect usedRect, NSTextContainer * _Nonnull usedTextContainer, NSRange lineGlyphRange, BOOL * _Nonnull stop) {
|
||||
NSRange range = [layoutManager characterRangeForGlyphRange:lineGlyphRange actualGlyphRange:nil];
|
||||
NSString *renderedString = [textStorage.string substringWithRange:range];
|
||||
UIFont *font = [[textStorage attributedSubstringFromRange:range] attribute:NSFontAttributeName atIndex:0 effectiveRange:nil];
|
||||
[lineData addObject:
|
||||
@{
|
||||
@"text": renderedString,
|
||||
@"x": @(usedRect.origin.x),
|
||||
@"y": @(usedRect.origin.y),
|
||||
@"width": @(usedRect.size.width),
|
||||
@"height": @(usedRect.size.height),
|
||||
@"descender": @(-font.descender),
|
||||
@"capHeight": @(font.capHeight),
|
||||
@"ascender": @(font.ascender),
|
||||
@"xHeight": @(font.xHeight),
|
||||
}];
|
||||
}];
|
||||
NSDictionary *payload =
|
||||
@{
|
||||
@"lines": lineData,
|
||||
};
|
||||
_onTextLayout(payload);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)lastBaselineForSize:(CGSize)size
|
||||
{
|
||||
NSAttributedString *attributedText =
|
||||
[self textStorageAndLayoutManagerThatFitsSize:size exclusiveOwnership:NO];
|
||||
|
||||
__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 RCTTextShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
|
||||
{
|
||||
CGSize maximumSize = (CGSize){
|
||||
widthMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(width),
|
||||
heightMode == YGMeasureModeUndefined ? CGFLOAT_MAX : RCTCoreGraphicsFloatFromYogaFloat(height),
|
||||
};
|
||||
|
||||
RCTTextShadowView *shadowTextView = (__bridge RCTTextShadowView *)YGNodeGetContext(node);
|
||||
|
||||
NSTextStorage *textStorage =
|
||||
[shadowTextView textStorageAndLayoutManagerThatFitsSize:maximumSize
|
||||
exclusiveOwnership:NO];
|
||||
|
||||
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
|
||||
|
||||
CGFloat letterSpacing = shadowTextView.textAttributes.letterSpacing;
|
||||
if (!isnan(letterSpacing) && letterSpacing < 0) {
|
||||
size.width -= letterSpacing;
|
||||
}
|
||||
|
||||
size = (CGSize){
|
||||
MIN(RCTCeilPixelValue(size.width), maximumSize.width),
|
||||
MIN(RCTCeilPixelValue(size.height), maximumSize.height)
|
||||
};
|
||||
|
||||
// Adding epsilon value illuminates problems with converting values from
|
||||
// `double` to `float`, and then rounding them to pixel grid in Yoga.
|
||||
CGFloat epsilon = 0.001;
|
||||
return (YGSize){
|
||||
RCTYogaFloatFromCoreGraphicsFloat(size.width + epsilon),
|
||||
RCTYogaFloatFromCoreGraphicsFloat(size.height + epsilon)
|
||||
};
|
||||
}
|
||||
|
||||
static float RCTTextShadowViewBaseline(YGNodeRef node, const float width, const float height)
|
||||
{
|
||||
RCTTextShadowView *shadowTextView = (__bridge RCTTextShadowView *)YGNodeGetContext(node);
|
||||
|
||||
CGSize size = (CGSize){
|
||||
RCTCoreGraphicsFloatFromYogaFloat(width),
|
||||
RCTCoreGraphicsFloatFromYogaFloat(height)
|
||||
};
|
||||
|
||||
CGFloat lastBaseline = [shadowTextView lastBaselineForSize:size];
|
||||
|
||||
return RCTYogaFloatFromCoreGraphicsFloat(lastBaseline);
|
||||
}
|
||||
|
||||
@end
|
24
node_modules/react-native/Libraries/Text/Text/RCTTextView.h
generated
vendored
Normal file
24
node_modules/react-native/Libraries/Text/Text/RCTTextView.h
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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/RCTComponent.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTTextView : UIView
|
||||
|
||||
@property (nonatomic, assign) BOOL selectable;
|
||||
|
||||
- (void)setTextStorage:(NSTextStorage *)textStorage
|
||||
contentFrame:(CGRect)contentFrame
|
||||
descendantViews:(NSArray<UIView *> *)descendantViews;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
269
node_modules/react-native/Libraries/Text/Text/RCTTextView.m
generated
vendored
Normal file
269
node_modules/react-native/Libraries/Text/Text/RCTTextView.m
generated
vendored
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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/RCTTextView.h>
|
||||
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import <React/RCTTextShadowView.h>
|
||||
|
||||
@implementation RCTTextView
|
||||
{
|
||||
CAShapeLayer *_highlightLayer;
|
||||
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
||||
|
||||
NSArray<UIView *> *_Nullable _descendantViews;
|
||||
NSTextStorage *_Nullable _textStorage;
|
||||
CGRect _contentFrame;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.isAccessibilityElement = YES;
|
||||
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
|
||||
self.opaque = NO;
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *superDescription = super.description;
|
||||
NSRange semicolonRange = [superDescription rangeOfString:@";"];
|
||||
NSString *replacement = [NSString stringWithFormat:@"; reactTag: %@; text: %@", self.reactTag, _textStorage.string];
|
||||
return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement];
|
||||
}
|
||||
|
||||
- (void)setSelectable:(BOOL)selectable
|
||||
{
|
||||
if (_selectable == selectable) {
|
||||
return;
|
||||
}
|
||||
|
||||
_selectable = selectable;
|
||||
|
||||
if (_selectable) {
|
||||
[self enableContextMenu];
|
||||
}
|
||||
else {
|
||||
[self disableContextMenu];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
// Text looks super weird if its frame is animated.
|
||||
// This disables the frame animation, without affecting opacity, etc.
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super reactSetFrame:frame];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are managed by `setTextStorage:` method
|
||||
}
|
||||
|
||||
- (void)setTextStorage:(NSTextStorage *)textStorage
|
||||
contentFrame:(CGRect)contentFrame
|
||||
descendantViews:(NSArray<UIView *> *)descendantViews
|
||||
{
|
||||
_textStorage = textStorage;
|
||||
_contentFrame = contentFrame;
|
||||
|
||||
// FIXME: Optimize this.
|
||||
for (UIView *view in _descendantViews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
_descendantViews = descendantViews;
|
||||
|
||||
for (UIView *view in descendantViews) {
|
||||
[self addSubview:view];
|
||||
}
|
||||
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
if (!_textStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
|
||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin];
|
||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin];
|
||||
|
||||
__block UIBezierPath *highlightPath = nil;
|
||||
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange
|
||||
actualGlyphRange:NULL];
|
||||
[_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName
|
||||
inRange:characterRange
|
||||
options:0
|
||||
usingBlock:
|
||||
^(NSNumber *value, NSRange range, __unused BOOL *stop) {
|
||||
if (!value.boolValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
[layoutManager enumerateEnclosingRectsForGlyphRange:range
|
||||
withinSelectedGlyphRange:range
|
||||
inTextContainer:textContainer
|
||||
usingBlock:
|
||||
^(CGRect enclosingRect, __unused BOOL *anotherStop) {
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2];
|
||||
if (highlightPath) {
|
||||
[highlightPath appendPath:path];
|
||||
} else {
|
||||
highlightPath = path;
|
||||
}
|
||||
}
|
||||
];
|
||||
}];
|
||||
|
||||
if (highlightPath) {
|
||||
if (!_highlightLayer) {
|
||||
_highlightLayer = [CAShapeLayer layer];
|
||||
_highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor;
|
||||
[self.layer addSublayer:_highlightLayer];
|
||||
}
|
||||
_highlightLayer.position = _contentFrame.origin;
|
||||
_highlightLayer.path = highlightPath.CGPath;
|
||||
} else {
|
||||
[_highlightLayer removeFromSuperlayer];
|
||||
_highlightLayer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSNumber *)reactTagAtPoint:(CGPoint)point
|
||||
{
|
||||
NSNumber *reactTag = self.reactTag;
|
||||
|
||||
CGFloat fraction;
|
||||
NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
|
||||
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
||||
NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
|
||||
inTextContainer:textContainer
|
||||
fractionOfDistanceBetweenInsertionPoints:&fraction];
|
||||
|
||||
// If the point is not before (fraction == 0.0) the first character and not
|
||||
// after (fraction == 1.0) the last character, then the attribute is valid.
|
||||
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
|
||||
reactTag = [_textStorage attribute:RCTTextAttributesTagAttributeName atIndex:characterIndex effectiveRange:NULL];
|
||||
}
|
||||
|
||||
return reactTag;
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
|
||||
if (!self.window) {
|
||||
self.layer.contents = nil;
|
||||
if (_highlightLayer) {
|
||||
[_highlightLayer removeFromSuperlayer];
|
||||
_highlightLayer = nil;
|
||||
}
|
||||
} else if (_textStorage) {
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
{
|
||||
NSString *superAccessibilityLabel = [super accessibilityLabel];
|
||||
if (superAccessibilityLabel) {
|
||||
return superAccessibilityLabel;
|
||||
}
|
||||
return _textStorage.string;
|
||||
}
|
||||
|
||||
#pragma mark - Context Menu
|
||||
|
||||
- (void)enableContextMenu
|
||||
{
|
||||
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
|
||||
[self addGestureRecognizer:_longPressGestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)disableContextMenu
|
||||
{
|
||||
[self removeGestureRecognizer:_longPressGestureRecognizer];
|
||||
_longPressGestureRecognizer = nil;
|
||||
}
|
||||
|
||||
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
|
||||
{
|
||||
// TODO: Adopt showMenuFromRect (necessary for UIKitForMac)
|
||||
#if !TARGET_OS_TV && !TARGET_OS_UIKITFORMAC
|
||||
UIMenuController *menuController = [UIMenuController sharedMenuController];
|
||||
|
||||
if (menuController.isMenuVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.isFirstResponder) {
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
|
||||
[menuController setTargetRect:self.bounds inView:self];
|
||||
[menuController setMenuVisible:YES animated:YES];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return _selectable;
|
||||
}
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
if (_selectable && action == @selector(copy:)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [self.nextResponder canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
- (void)copy:(id)sender
|
||||
{
|
||||
#if !TARGET_OS_TV
|
||||
NSAttributedString *attributedText = _textStorage;
|
||||
|
||||
NSMutableDictionary *item = [NSMutableDictionary new];
|
||||
|
||||
NSData *rtf = [attributedText dataFromRange:NSMakeRange(0, attributedText.length)
|
||||
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
|
||||
error:nil];
|
||||
|
||||
if (rtf) {
|
||||
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
|
||||
}
|
||||
|
||||
[item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText];
|
||||
|
||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||
pasteboard.items = @[item];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
14
node_modules/react-native/Libraries/Text/Text/RCTTextViewManager.h
generated
vendored
Normal file
14
node_modules/react-native/Libraries/Text/Text/RCTTextViewManager.h
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#import "RCTBaseTextViewManager.h"
|
||||
|
||||
@interface RCTTextViewManager : RCTBaseTextViewManager
|
||||
|
||||
@end
|
94
node_modules/react-native/Libraries/Text/Text/RCTTextViewManager.m
generated
vendored
Normal file
94
node_modules/react-native/Libraries/Text/Text/RCTTextViewManager.m
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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/RCTTextViewManager.h>
|
||||
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUIManagerUtils.h>
|
||||
#import <React/RCTUIManagerObserverCoordinator.h>
|
||||
|
||||
#import <React/RCTTextShadowView.h>
|
||||
#import <React/RCTTextView.h>
|
||||
|
||||
@interface RCTTextViewManager () <RCTUIManagerObserver>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTextViewManager
|
||||
{
|
||||
NSHashTable<RCTTextShadowView *> *_shadowViews;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(RCTText)
|
||||
|
||||
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger)
|
||||
RCT_REMAP_SHADOW_PROPERTY(ellipsizeMode, lineBreakMode, NSLineBreakMode)
|
||||
RCT_REMAP_SHADOW_PROPERTY(adjustsFontSizeToFit, adjustsFontSizeToFit, BOOL)
|
||||
RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat)
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(onTextLayout, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)
|
||||
|
||||
- (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]];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [RCTTextView new];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
RCTTextShadowView *shadowView = [[RCTTextShadowView alloc] initWithBridge:self.bridge];
|
||||
shadowView.textAttributes.fontSizeMultiplier = [[[self.bridge moduleForName:@"AccessibilityManager"]
|
||||
valueForKey:@"multiplier"] floatValue];
|
||||
[_shadowViews addObject:shadowView];
|
||||
return shadowView;
|
||||
}
|
||||
|
||||
#pragma mark - RCTUIManagerObserver
|
||||
|
||||
- (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager
|
||||
{
|
||||
for (RCTTextShadowView *shadowView in _shadowViews) {
|
||||
[shadowView uiManagerWillPerformMounting];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Font Size Multiplier
|
||||
|
||||
- (void)handleDidUpdateMultiplierNotification
|
||||
{
|
||||
CGFloat fontSizeMultiplier = [[[self.bridge moduleForName:@"AccessibilityManager"]
|
||||
valueForKey:@"multiplier"] floatValue];
|
||||
|
||||
NSHashTable<RCTTextShadowView *> *shadowViews = _shadowViews;
|
||||
RCTExecuteOnUIManagerQueue(^{
|
||||
for (RCTTextShadowView *shadowView in shadowViews) {
|
||||
shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier;
|
||||
[shadowView dirtyLayout];
|
||||
}
|
||||
|
||||
[self.bridge.uiManager setNeedsLayout];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
18
node_modules/react-native/Libraries/Text/TextAncestor.js
generated
vendored
Normal file
18
node_modules/react-native/Libraries/Text/TextAncestor.js
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
|
||||
/**
|
||||
* Whether the current element is the descendant of a <Text> element.
|
||||
*/
|
||||
module.exports = (React.createContext(false): React$Context<$FlowFixMe>);
|
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
|
190
node_modules/react-native/Libraries/Text/TextProps.js
generated
vendored
Normal file
190
node_modules/react-native/Libraries/Text/TextProps.js
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {
|
||||
LayoutEvent,
|
||||
PressEvent,
|
||||
TextLayoutEvent,
|
||||
} from '../Types/CoreEventTypes';
|
||||
import type {Node} from 'react';
|
||||
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
|
||||
import type {
|
||||
AccessibilityRole,
|
||||
AccessibilityState,
|
||||
} from '../Components/View/ViewAccessibility';
|
||||
|
||||
export type PressRetentionOffset = $ReadOnly<{|
|
||||
top: number,
|
||||
left: number,
|
||||
bottom: number,
|
||||
right: number,
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* @see https://reactnative.dev/docs/text.html#reference
|
||||
*/
|
||||
export type TextProps = $ReadOnly<{|
|
||||
/**
|
||||
* Indicates whether the view is an accessibility element.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#accessible
|
||||
*/
|
||||
accessible?: ?boolean,
|
||||
accessibilityHint?: ?Stringish,
|
||||
accessibilityLabel?: ?Stringish,
|
||||
accessibilityRole?: ?AccessibilityRole,
|
||||
accessibilityState?: ?AccessibilityState,
|
||||
|
||||
/**
|
||||
* Whether font should be scaled down automatically.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#adjustsfontsizetofit
|
||||
*/
|
||||
adjustsFontSizeToFit?: ?boolean,
|
||||
|
||||
/**
|
||||
* Whether fonts should scale to respect Text Size accessibility settings.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#allowfontscaling
|
||||
*/
|
||||
allowFontScaling?: ?boolean,
|
||||
children?: ?Node,
|
||||
|
||||
/**
|
||||
* When `numberOfLines` is set, this prop defines how text will be
|
||||
* truncated.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#ellipsizemode
|
||||
*/
|
||||
ellipsizeMode?: ?('clip' | 'head' | 'middle' | 'tail'),
|
||||
|
||||
/**
|
||||
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
|
||||
* Possible values:
|
||||
* `null/undefined` (default): inherit from the parent node or the global default (0)
|
||||
* `0`: no max, ignore parent/global default
|
||||
* `>= 1`: sets the maxFontSizeMultiplier of this node to this value
|
||||
*/
|
||||
maxFontSizeMultiplier?: ?number,
|
||||
|
||||
/**
|
||||
* Used to locate this view from native code.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#nativeid
|
||||
*/
|
||||
nativeID?: ?string,
|
||||
|
||||
/**
|
||||
* Used to truncate the text with an ellipsis.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#numberoflines
|
||||
*/
|
||||
numberOfLines?: ?number,
|
||||
|
||||
/**
|
||||
* Invoked on mount and layout changes.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#onlayout
|
||||
*/
|
||||
onLayout?: ?(event: LayoutEvent) => mixed,
|
||||
|
||||
/**
|
||||
* This function is called on long press.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#onlongpress
|
||||
*/
|
||||
onLongPress?: ?(event: PressEvent) => mixed,
|
||||
|
||||
/**
|
||||
* This function is called on press.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#onpress
|
||||
*/
|
||||
onPress?: ?(event: PressEvent) => mixed,
|
||||
onResponderGrant?: ?(event: PressEvent, dispatchID: string) => void,
|
||||
onResponderMove?: ?(event: PressEvent) => void,
|
||||
onResponderRelease?: ?(event: PressEvent) => void,
|
||||
onResponderTerminate?: ?(event: PressEvent) => void,
|
||||
onResponderTerminationRequest?: ?() => boolean,
|
||||
onStartShouldSetResponder?: ?() => boolean,
|
||||
onMoveShouldSetResponder?: ?() => boolean,
|
||||
onTextLayout?: ?(event: TextLayoutEvent) => mixed,
|
||||
|
||||
/**
|
||||
* Defines how far your touch may move off of the button, before
|
||||
* deactivating the button.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#pressretentionoffset
|
||||
*/
|
||||
pressRetentionOffset?: ?PressRetentionOffset,
|
||||
|
||||
/**
|
||||
* Lets the user select text.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#selectable
|
||||
*/
|
||||
selectable?: ?boolean,
|
||||
style?: ?TextStyleProp,
|
||||
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#testid
|
||||
*/
|
||||
testID?: ?string,
|
||||
|
||||
/**
|
||||
* Android Only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specifies the disabled state of the text view for testing purposes.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#disabled
|
||||
*/
|
||||
disabled?: ?boolean,
|
||||
|
||||
/**
|
||||
* The highlight color of the text.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#selectioncolor
|
||||
*/
|
||||
selectionColor?: ?string,
|
||||
|
||||
dataDetectorType?: ?('phoneNumber' | 'link' | 'email' | 'none' | 'all'),
|
||||
|
||||
/**
|
||||
* Set text break strategy on Android.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#textbreakstrategy
|
||||
*/
|
||||
textBreakStrategy?: ?('balanced' | 'highQuality' | 'simple'),
|
||||
|
||||
/**
|
||||
* iOS Only
|
||||
*/
|
||||
adjustsFontSizeToFit?: ?boolean,
|
||||
|
||||
/**
|
||||
* Smallest possible scale a font can reach.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#minimumfontscale
|
||||
*/
|
||||
minimumFontScale?: ?number,
|
||||
|
||||
/**
|
||||
* When `true`, no visual change is made when text is pressed down.
|
||||
*
|
||||
* See https://reactnative.dev/docs/text.html#supperhighlighting
|
||||
*/
|
||||
suppressHighlighting?: ?boolean,
|
||||
|}>;
|
12
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.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 "RCTBaseTextShadowView.h"
|
||||
|
||||
@interface RCTVirtualTextShadowView : RCTBaseTextShadowView
|
||||
|
||||
@end
|
38
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.m
generated
vendored
Normal file
38
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextShadowView.m
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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/RCTVirtualTextShadowView.h>
|
||||
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <yoga/Yoga.h>
|
||||
|
||||
#import <React/RCTRawTextShadowView.h>
|
||||
|
||||
@implementation RCTVirtualTextShadowView {
|
||||
BOOL _isLayoutDirty;
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)dirtyLayout
|
||||
{
|
||||
[super dirtyLayout];
|
||||
|
||||
if (_isLayoutDirty) {
|
||||
return;
|
||||
}
|
||||
_isLayoutDirty = YES;
|
||||
|
||||
[self.superview dirtyLayout];
|
||||
}
|
||||
|
||||
- (void)clearLayout
|
||||
{
|
||||
_isLayoutDirty = NO;
|
||||
}
|
||||
|
||||
@end
|
12
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.h
generated
vendored
Normal file
12
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.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 RCTVirtualTextViewManager : RCTBaseTextViewManager
|
||||
|
||||
@end
|
26
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.m
generated
vendored
Normal file
26
node_modules/react-native/Libraries/Text/VirtualText/RCTVirtualTextViewManager.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/RCTVirtualTextViewManager.h>
|
||||
|
||||
#import <React/RCTVirtualTextShadowView.h>
|
||||
|
||||
@implementation RCTVirtualTextViewManager
|
||||
|
||||
RCT_EXPORT_MODULE(RCTVirtualText)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [UIView new];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [RCTVirtualTextShadowView new];
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user