From 6513a917ce54510f5712537f17d5eeddbab74442 Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Fri, 28 Apr 2023 13:54:54 +0200 Subject: [PATCH 1/3] feat: add caretHeight and caretYoffset to TextInput component --- Libraries/Components/TextInput/TextInput.d.ts | 12 ++++ .../Components/TextInput/TextInput.flow.js | 12 ++++ Libraries/Components/TextInput/TextInput.js | 13 ++++ .../Text/TextInput/Multiline/RCTUITextView.h | 2 + .../Text/TextInput/Multiline/RCTUITextView.m | 69 ++++++++++++++++++- .../RCTBackedTextInputViewProtocol.h | 2 + .../TextInput/RCTBaseTextInputViewManager.m | 2 + .../TextInput/RCTTextInputComponentView.mm | 8 +++ .../TextInput/RCTTextInputUtils.mm | 2 + .../textinput/iostextinput/primitives.h | 13 ++++ .../textinput/iostextinput/propsConversions.h | 12 ++++ 11 files changed, 146 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/TextInput/TextInput.d.ts b/Libraries/Components/TextInput/TextInput.d.ts index 3f0159701e00eb..bc57a0cb754a0e 100644 --- a/Libraries/Components/TextInput/TextInput.d.ts +++ b/Libraries/Components/TextInput/TextInput.d.ts @@ -573,6 +573,18 @@ export interface TextInputProps */ caretHidden?: boolean | undefined; + /** + * Allows to adjust caret height. + * The default value is 0, which means the height of the caret will be calculated automatically + */ + caretHeight?: number | undefined; + + /** + * Allows to adjust caret postiion relative to the Y axis + * The default value is 0. + */ + caretYOffset?: number | undefined; + /** * If true, context menu is hidden. The default value is false. */ diff --git a/Libraries/Components/TextInput/TextInput.flow.js b/Libraries/Components/TextInput/TextInput.flow.js index 42594c7aa13ef5..2629ae58e28e1d 100644 --- a/Libraries/Components/TextInput/TextInput.flow.js +++ b/Libraries/Components/TextInput/TextInput.flow.js @@ -557,6 +557,18 @@ export type Props = $ReadOnly<{| */ caretHidden?: ?boolean, + /** + * Allows to adjust caret height. + * The default value is 0, which means the height of the caret will be calculated automatically + */ + + caretYOffset?: ?number, + /** + * Allows to adjust caret postiion relative to the Y axis + * The default value is 0. + */ + caretYHeight?: ?number, + /* * If `true`, contextMenuHidden is hidden. The default value is `false`. */ diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 764e2392a5ca67..2210ef9897b33a 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -368,6 +368,17 @@ type IOSProps = $ReadOnly<{| * @platform ios */ smartInsertDelete?: ?boolean, + /** + * Allows to adjust caret height. + * The default value is 0, which means the height of the caret will be calculated automatically + */ + caretYOffset?: ?number, + + /** + * Allows to adjust caret postiion relative to the Y axis + * The default value is 0. + */ + caretHeight?: ?number, |}>; type AndroidProps = $ReadOnly<{| @@ -1459,6 +1470,8 @@ function InternalTextInput(props: Props): React.Node { selection={selection} style={style} text={text} + caretYOffset={props.caretYOffset} + caretHeight={props.caretHeight} /> ); } else if (Platform.OS === 'android') { diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/Libraries/Text/TextInput/Multiline/RCTUITextView.h index 1215ff0843402f..f32bb865124507 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.h @@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UITextFieldViewMode clearButtonMode; @property (nonatomic, assign) BOOL caretHidden; +@property (nonatomic, assign) CGFloat caretYOffset; +@property (nonatomic, assign) CGFloat caretHeight; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 62ec0d5354a52d..1bdbbcc9704270 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -13,6 +13,41 @@ #import #import +//the UITextSelectionRect subclass needs to be created because the original version is not writable +@interface CustomTextSelectionRect : UITextSelectionRect + +@property (nonatomic) CGRect _rect; +@property (nonatomic) NSWritingDirection _writingDirection; +@property (nonatomic) BOOL _containsStart; // Returns YES if the rect contains the start of the selection. +@property (nonatomic) BOOL _containsEnd; // Returns YES if the rect contains the end of the selection. +@property (nonatomic) BOOL _isVertical; // Returns YES if the rect is for vertically oriented text. + +@end + +@implementation CustomTextSelectionRect + +- (CGRect)rect { + return __rect; +} + +- (NSWritingDirection)writingDirection { + return __writingDirection; +} + +- (BOOL)containsStart { + return __containsStart; +} + +- (BOOL)containsEnd { + return __containsEnd; +} + +- (BOOL)isVertical { + return __isVertical; +} + +@end + @implementation RCTUITextView { UILabel *_placeholderView; UITextView *_detachedTextView; @@ -294,11 +329,43 @@ - (void)_updatePlaceholder - (CGRect)caretRectForPosition:(UITextPosition *)position { + CGRect originalRect = [super caretRectForPosition:position]; + if (_caretHidden) { return CGRectZero; } - return [super caretRectForPosition:position]; + if(_caretYOffset != 0) { + originalRect.origin.y += _caretYOffset; + } + + if(_caretHeight != 0) { + originalRect.size.height = _caretHeight; + } + return originalRect; +} + +- (NSArray *)selectionRectsForRange:(UITextRange *)range { + NSArray *superRects = [super selectionRectsForRange:range]; + if(_caretYOffset != 0 && _caretHeight != 0) { + NSMutableArray *customTextSelectionRects = [NSMutableArray array]; + + for (UITextSelectionRect *rect in superRects) { + CustomTextSelectionRect *customTextRect = [[CustomTextSelectionRect alloc] init]; + + customTextRect._rect = CGRectMake(rect.rect.origin.x, rect.rect.origin.y + _caretYOffset, rect.rect.size.width, _caretHeight); + customTextRect._writingDirection = rect.writingDirection; + customTextRect._containsStart = rect.containsStart; + customTextRect._containsEnd = rect.containsEnd; + customTextRect._isVertical = rect.isVertical; + [customTextSelectionRects addObject:customTextRect]; + } + + return customTextSelectionRects; + + } + return superRects; + } #pragma mark - Utility Methods diff --git a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 686af9e3a363e2..02c76a7774535c 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL contextMenuHidden; @property (nonatomic, assign, getter=isEditable) BOOL editable; @property (nonatomic, assign) BOOL caretHidden; +@property (nonatomic, assign) CGFloat caretYOffset; +@property (nonatomic, assign) CGFloat caretHeight; @property (nonatomic, assign) BOOL enablesReturnKeyAutomatically; @property (nonatomic, assign) UITextFieldViewMode clearButtonMode; @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; diff --git a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m index a19b55569e8d71..d6915ee37335ce 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m @@ -44,6 +44,8 @@ @implementation RCTBaseTextInputViewManager { 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(caretYOffset, backedTextInputView.caretYOffset, CGFloat) +RCT_REMAP_VIEW_PROPERTY(caretHeight, backedTextInputView.caretHeight, CGFloat) RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode) RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL) RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL) diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 3d8b7f7b06f824..1fd824fbecf37c 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -153,6 +153,14 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden; } + if(newTextInputProps.traits.caretYOffset != oldTextInputProps.traits.caretYOffset) { + _backedTextInputView.caretYOffset = newTextInputProps.traits.caretYOffset; + } + + if(newTextInputProps.traits.caretHeight != oldTextInputProps.traits.caretHeight) { + _backedTextInputView.caretHeight = newTextInputProps.traits.caretHeight; + } + if (newTextInputProps.traits.clearButtonMode != oldTextInputProps.traits.clearButtonMode) { _backedTextInputView.clearButtonMode = RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(newTextInputProps.traits.clearButtonMode); diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 41343e1c75db22..9967383f19a595 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -38,6 +38,8 @@ void RCTCopyBackedTextInput( toTextInput.keyboardAppearance = fromTextInput.keyboardAppearance; toTextInput.spellCheckingType = fromTextInput.spellCheckingType; toTextInput.caretHidden = fromTextInput.caretHidden; + toTextInput.caretYOffset = fromTextInput.caretYOffset; + toTextInput.caretHeight = toTextInput.caretHeight; toTextInput.clearButtonMode = fromTextInput.clearButtonMode; toTextInput.scrollEnabled = fromTextInput.scrollEnabled; toTextInput.secureTextEntry = fromTextInput.secureTextEntry; diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h b/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h index bfcf0f45127e54..31fd42bf6c5926 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h @@ -156,6 +156,19 @@ class TextInputTraits final { */ bool caretHidden{false}; + /* + * iOS-only (inherently iOS-specific) + * Default value: 0 with a default font. + */ + + int caretHeight{0}; + + /* + * iOS-only (inherently iOS-specific) + * Default value: 0 means that the caret offset will have the default value + */ + int caretYOffset{0}; + /* * Controls the visibility of a `Clean` button. * iOS-only (implemented only on iOS for now) diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/propsConversions.h b/ReactCommon/react/renderer/components/textinput/iostextinput/propsConversions.h index fc03b469997f26..ad30c578e738be 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/propsConversions.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/propsConversions.h @@ -75,6 +75,18 @@ static TextInputTraits convertRawProp( "caretHidden", sourceTraits.caretHidden, defaultTraits.caretHidden); + traits.caretYOffset = convertRawProp( + context, + rawProps, + "caretYOffset", + sourceTraits.caretYOffset, + defaultTraits.caretYOffset); + traits.caretHeight= convertRawProp( + context, + rawProps, + "caretHeight", + sourceTraits.caretHeight, + defaultTraits.caretHeight); traits.clearButtonMode = convertRawProp( context, rawProps, From b85913b71d4ecef0ed5e7f1df6b98fe45127a96f Mon Sep 17 00:00:00 2001 From: OlimpiaZurek Date: Thu, 4 May 2023 11:36:18 +0200 Subject: [PATCH 2/3] minor changes after code review --- Libraries/Components/TextInput/TextInput.d.ts | 4 +++- Libraries/Components/TextInput/TextInput.flow.js | 6 ++++-- Libraries/Components/TextInput/TextInput.js | 6 +++--- Libraries/Text/TextInput/Multiline/RCTUITextView.m | 5 +++-- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm | 2 +- .../renderer/components/textinput/iostextinput/primitives.h | 1 - 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.d.ts b/Libraries/Components/TextInput/TextInput.d.ts index bc57a0cb754a0e..ce135b84e0bb82 100644 --- a/Libraries/Components/TextInput/TextInput.d.ts +++ b/Libraries/Components/TextInput/TextInput.d.ts @@ -576,12 +576,14 @@ export interface TextInputProps /** * Allows to adjust caret height. * The default value is 0, which means the height of the caret will be calculated automatically + * @platform ios */ caretHeight?: number | undefined; /** - * Allows to adjust caret postiion relative to the Y axis + * Allows to adjust caret position relative to the Y axis * The default value is 0. + * @platform ios */ caretYOffset?: number | undefined; diff --git a/Libraries/Components/TextInput/TextInput.flow.js b/Libraries/Components/TextInput/TextInput.flow.js index 2629ae58e28e1d..d5b101cf62ddbd 100644 --- a/Libraries/Components/TextInput/TextInput.flow.js +++ b/Libraries/Components/TextInput/TextInput.flow.js @@ -560,12 +560,14 @@ export type Props = $ReadOnly<{| /** * Allows to adjust caret height. * The default value is 0, which means the height of the caret will be calculated automatically + * @platform ios */ - caretYOffset?: ?number, + /** - * Allows to adjust caret postiion relative to the Y axis + * Allows to adjust caret position relative to the Y axis * The default value is 0. + * @platform ios */ caretYHeight?: ?number, diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 2210ef9897b33a..46dcd3e9c383ae 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -371,12 +371,14 @@ type IOSProps = $ReadOnly<{| /** * Allows to adjust caret height. * The default value is 0, which means the height of the caret will be calculated automatically + * @platform ios */ caretYOffset?: ?number, /** - * Allows to adjust caret postiion relative to the Y axis + * Allows to adjust caret position relative to the Y axis * The default value is 0. + * @platform ios */ caretHeight?: ?number, |}>; @@ -1470,8 +1472,6 @@ function InternalTextInput(props: Props): React.Node { selection={selection} style={style} text={text} - caretYOffset={props.caretYOffset} - caretHeight={props.caretHeight} /> ); } else if (Platform.OS === 'android') { diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 1bdbbcc9704270..26ddf7b6796744 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -339,10 +339,11 @@ - (CGRect)caretRectForPosition:(UITextPosition *)position originalRect.origin.y += _caretYOffset; } - if(_caretHeight != 0) { + if(_caretHeight != 0) { originalRect.size.height = _caretHeight; } - return originalRect; + + return originalRect; } - (NSArray *)selectionRectsForRange:(UITextRange *)range { diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 9967383f19a595..099d47e6a9b3bd 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -39,7 +39,7 @@ void RCTCopyBackedTextInput( toTextInput.spellCheckingType = fromTextInput.spellCheckingType; toTextInput.caretHidden = fromTextInput.caretHidden; toTextInput.caretYOffset = fromTextInput.caretYOffset; - toTextInput.caretHeight = toTextInput.caretHeight; + toTextInput.caretHeight = fromTextInput.caretHeight; toTextInput.clearButtonMode = fromTextInput.clearButtonMode; toTextInput.scrollEnabled = fromTextInput.scrollEnabled; toTextInput.secureTextEntry = fromTextInput.secureTextEntry; diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h b/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h index 31fd42bf6c5926..f0c50d96bd0736 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/primitives.h @@ -160,7 +160,6 @@ class TextInputTraits final { * iOS-only (inherently iOS-specific) * Default value: 0 with a default font. */ - int caretHeight{0}; /* From a8ea2102ecb0cbedcc0ef77c6f90ee6c4d8d1766 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 25 Jul 2023 13:58:56 +0200 Subject: [PATCH 3/3] fix: minor fixes --- .../react-native/Libraries/Components/TextInput/TextInput.js | 1 + .../Libraries/Text/TextInput/Multiline/RCTUITextView.m | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 780d6bfe734fa2..c30044fabf1b3a 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -376,6 +376,7 @@ type IOSProps = $ReadOnly<{| * @platform ios */ smartInsertDelete?: ?boolean, + /** * Allows to adjust caret height. * The default value is 0, which means the height of the caret will be calculated automatically diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m index d7f9bb749de53b..df1982c579a789 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -13,7 +13,7 @@ #import #import -//the UITextSelectionRect subclass needs to be created because the original version is not writable +// the UITextSelectionRect subclass needs to be created because the original version is not writable @interface CustomTextSelectionRect : UITextSelectionRect @property (nonatomic) CGRect _rect;