-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TextInput] Add numberOfLines and maximumNumberOfLines props on iOS and fix Android implementation #35703
[TextInput] Add numberOfLines and maximumNumberOfLines props on iOS and fix Android implementation #35703
Changes from 9 commits
c9e11d5
e55eed8
cd78e0d
34acc74
22d7189
e5388e1
448a5eb
8080b34
b58d914
b29648d
036d5ad
24614c6
2551355
e367bc0
f77567d
4bfc7be
bac869c
cfb5701
d5dfc99
f7d9693
ef6fc1c
39709bb
c3e1c41
5a90e0d
908eeca
bcb467d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{| | |
*/ | ||
numberOfLines?: ?Int32, | ||
|
||
/** | ||
* Sets the maximum number of lines for a `TextInput`. Use it with multiline set to | ||
* `true` to be able to fill the lines. | ||
* @platform android | ||
*/ | ||
maximumNumberOfLines?: ?Int32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we name this React Native isn't totally consistent with this since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also cc @necolas who I saw had some comments around web prop naming. I can see that there isn't a direct equivalent to this on web, so the current naming here (augmenting the well-used RN prop) makes sense (unless there is work to actually remove the old ones). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe maxRows? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just renamed it in InternalTextInput & flow & TS so that end user can pass |
||
|
||
/** | ||
* When `false`, if there is a small amount of space available around a text input | ||
* (e.g. landscape orientation on a phone), the OS may choose to have the user edit | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,7 @@ const Text: React.AbstractComponent< | |
pressRetentionOffset, | ||
role, | ||
suppressHighlighting, | ||
numberOfLines, | ||
...restProps | ||
} = props; | ||
|
||
|
@@ -192,12 +193,12 @@ const Text: React.AbstractComponent< | |
} | ||
} | ||
|
||
let numberOfLines = restProps.numberOfLines; | ||
let numberOfLinesValue = numberOfLines; | ||
if (numberOfLines != null && !(numberOfLines >= 0)) { | ||
console.error( | ||
`'numberOfLines' in <Text> must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`, | ||
); | ||
numberOfLines = 0; | ||
numberOfLinesValue = 0; | ||
} | ||
|
||
const hasTextAncestor = useContext(TextAncestor); | ||
|
@@ -241,7 +242,7 @@ const Text: React.AbstractComponent< | |
isHighlighted={isHighlighted} | ||
isPressable={isPressable} | ||
nativeID={id ?? nativeID} | ||
numberOfLines={numberOfLines} | ||
maximumNumberOfLines={numberOfLinesValue} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know the old code passed this too, but I think this prop may always no-op in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so too. Do you think it's better to remove it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed! |
||
ref={forwardedRef} | ||
selectable={_selectable} | ||
selectionColor={selectionColor} | ||
|
@@ -267,7 +268,7 @@ const Text: React.AbstractComponent< | |
ellipsizeMode={ellipsizeMode ?? 'tail'} | ||
isHighlighted={isHighlighted} | ||
nativeID={id ?? nativeID} | ||
numberOfLines={numberOfLines} | ||
maximumNumberOfLines={numberOfLinesValue} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in Fabric we are already mapping it on the native side as Text component it sets maximumNumberOfLines on textAttributes or paragraphAttributes. There were few native classes shared between TextInputComponent and Text Component and this remapping on the native side made it much cleaner. I can find exactly the example if you want but that seems to be reasonable :) . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we resolve #2 here? We need to ship this as:
So, we will want to split this change into separate ones for native and JS, where native changes are merged before the JS changes. In general I would recommend having separate PRs for platforms/sections anyways. I know I personally put quicker to review changes higher up on my priority queue. |
||
ref={forwardedRef} | ||
selectable={_selectable} | ||
selectionColor={selectionColor} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ | |
|
||
#import <React/RCTMultilineTextInputView.h> | ||
#import <React/RCTMultilineTextInputViewManager.h> | ||
#import <React/RCTUITextView.h> | ||
#import <React/RCTBaseTextInputShadowView.h> | ||
|
||
@implementation RCTMultilineTextInputViewManager | ||
|
||
|
@@ -17,8 +19,21 @@ - (UIView *)view | |
return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; | ||
} | ||
|
||
- (RCTShadowView *)shadowView | ||
{ | ||
RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; | ||
|
||
shadowView.maximumNumberOfLines = 0; | ||
shadowView.exactNumberOfLines = 0; | ||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can we use a different value from Same below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that values was used on android in shadow nodes so I just copied it. |
||
|
||
return shadowView; | ||
} | ||
|
||
#pragma mark - Multiline <TextInput> (aka TextView) specific properties | ||
|
||
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) | ||
|
||
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) | ||
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger) | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN | |
@property (nonatomic, copy, nullable) NSString *text; | ||
@property (nonatomic, copy, nullable) NSString *placeholder; | ||
@property (nonatomic, assign) NSInteger maximumNumberOfLines; | ||
@property (nonatomic, assign) NSInteger exactNumberOfLines; | ||
Comment on lines
18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on fabric yes |
||
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; | ||
|
||
- (void)uiManagerWillPerformMounting; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText | |
|
||
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize | ||
{ | ||
NSAttributedString *attributedText = [self measurableAttributedText]; | ||
NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm reading this correctly, we never mutate the previous AttributedString, but may completely replace it with the filler line variant to do a different measurement. Could we then avoid the copy, and just use a different pointer if we want to operate on something different? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I remember we need to sometimes add characters otherwise the size is not computed properly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I think on iOS we only add newlines but still we need to modify the object. |
||
|
||
/* | ||
* The block below is responsible for setting the exact height of the view in lines | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a question for @sammy-SC, is this kosher with any sort of AttributedString layout caching we are doing? I.e. is there something we need to cache bust if layout behavior here varies based on more of the number of line props? |
||
* Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines | ||
* prop and then add random lines at the front. However, they are only used for layout | ||
* so they are not visible on the screen. | ||
*/ | ||
if (self.exactNumberOfLines) { | ||
NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines]; | ||
for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) { | ||
[newLines appendString:@"\n"]; | ||
} | ||
[attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the AttributedString still have all of the various text props that were set? Text can have things like, different font size in different parts, etc. I'm not sure the most sane way to handle this prop in combination with various metrics. E.g. we can have an outer TextInput that customizes font size, then a span on the first character which changes it to something else? Which do we pick (it seems like in the java case, we copy all the spans over the beginning of the text). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it actually works only when we use same style for the whole TextInput component. I think it makes sense. Let's imagine someone passes Text component as children to textInput and every line has different size. How should that behave? Which line height should we use for missing lines? |
||
_maximumNumberOfLines = self.exactNumberOfLines; | ||
} | ||
|
||
if (!_textStorage) { | ||
_textContainer = [NSTextContainer new]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -709,6 +709,9 @@ dependencies { | |
androidTestImplementation("androidx.test:rules:${ANDROIDX_TEST_VERSION}") | ||
androidTestImplementation("org.mockito:mockito-core:${MOCKITO_CORE_VERSION}") | ||
|
||
api('com.parse.bolts:bolts-tasks:1.4.0') | ||
api('com.parse.bolts:bolts-applinks:1.4.0') | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bad merge? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think someone broke RN Tester when I was updating that pr and this changes has fixed it. Will remove it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed. Looks like RNTester has been fixed since then. |
||
// This compileOnly dependency is needed to be able to update the offline | ||
// mirror from a non-linux machine, while still executing inside a Linux CI | ||
// as we declare a dependency on aap2 @linux so we're sure the linux artifact | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -328,6 +328,7 @@ protected Spannable spannedFromShadowNode( | |
protected boolean mIsAccessibilityLink = false; | ||
|
||
protected int mNumberOfLines = UNSET; | ||
protected int mMaxNumberOfLines = UNSET; | ||
protected int mTextAlign = Gravity.NO_GRAVITY; | ||
protected int mTextBreakStrategy = | ||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; | ||
|
@@ -412,6 +413,12 @@ public void setNumberOfLines(int numberOfLines) { | |
markUpdated(); | ||
} | ||
|
||
@ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET) | ||
public void setMaxNumberOfLines(int numberOfLines) { | ||
mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why treat zero as unset? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it was already the case before with numberOfLines. But seems to have sense. iOS NSTextContainer also uses it as the default value. |
||
markUpdated(); | ||
} | ||
|
||
@ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) | ||
public void setLineHeight(float lineHeight) { | ||
mTextAttributes.setLineHeight(lineHeight); | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -49,8 +49,8 @@ public void setAccessible(ReactTextView view, boolean accessible) { | |||
} | ||||
|
||||
// maxLines can only be set in master view (block), doesn't really make sense to set in a span | ||||
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES) | ||||
public void setNumberOfLines(ReactTextView view, int numberOfLines) { | ||||
@ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = ViewDefaults.MAXIMUM_NUMBER_OF_LINES) | ||||
public void setMaxNumberOfLines(ReactTextView view, int numberOfLines) { | ||||
Comment on lines
+52
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, same question as earlier. Are we changing behavior of the existing prop? Seems like this removes the old one in favor of the new one (breaking change). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really as on the js side it's just mapped. react-native/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphProps.cpp Line 81 in 247a913
|
||||
view.setNumberOfLines(numberOfLines); | ||||
} | ||||
|
||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this related to your change?