Skip to content
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

Add support for numberOfLines and maximumNumberOfLines props on iOS and Android #37

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,

/**
* 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
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Components/TextInput/RCTTextInputViewConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ const RCTTextInputViewConfig = {
placeholder: true,
autoCorrect: true,
multiline: true,
numberOfLines: true,
maximumNumberOfLines: true,
textContentType: true,
maxLength: true,
autoCapitalize: true,
Expand Down
24 changes: 18 additions & 6 deletions Libraries/Components/TextInput/TextInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,12 +422,6 @@ export interface TextInputAndroidProps {
*/
inlineImagePadding?: number | undefined;

/**
* Sets the number of lines for a TextInput.
* Use it with multiline set to true to be able to fill the lines.
*/
numberOfLines?: number | undefined;

/**
* Sets the return key to the label. Use it instead of `returnKeyType`.
* @platform android
Expand Down Expand Up @@ -617,11 +611,29 @@ export interface TextInputProps
*/
maxLength?: number | undefined;

/**
* Sets the maximum number of lines for a TextInput.
* Use it with multiline set to true to be able to fill the lines.
*/
maximumNumberOfLines?: number | undefined;

/**
* If true, the text input can be multiple lines. The default value is false.
*/
multiline?: boolean | undefined;

/**
* Sets the number of lines for a TextInput.
* Use it with multiline set to true to be able to fill the lines.
*/
numberOfLines?: number | undefined;

/**
* Sets the number of rows for a TextInput.
* Use it with multiline set to true to be able to fill the lines.
*/
rows?: number | undefined;

/**
* Callback that is called when the text input is blurred
*/
Expand Down
32 changes: 18 additions & 14 deletions Libraries/Components/TextInput/TextInput.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,26 +488,12 @@ type AndroidProps = $ReadOnly<{|
*/
inlineImagePadding?: ?number,

/**
* Sets the number of lines for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
* @platform android
*/
numberOfLines?: ?number,

/**
* Sets the return key to the label. Use it instead of `returnKeyType`.
* @platform android
*/
returnKeyLabel?: ?string,

/**
* Sets the number of rows for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
* @platform android
*/
rows?: ?number,

/**
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
* Defaults to `true`.
Expand Down Expand Up @@ -656,6 +642,12 @@ export type Props = $ReadOnly<{|
*/
keyboardType?: ?KeyboardType,

/**
* Sets the maximum number of lines for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
*/
maximumNumberOfLines?: ?number,

/**
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
* Possible values:
Expand All @@ -677,6 +669,12 @@ export type Props = $ReadOnly<{|
*/
multiline?: ?boolean,

/**
* Sets the number of lines for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
*/
numberOfLines?: ?number,

/**
* Callback that is called when the text input is blurred.
*/
Expand Down Expand Up @@ -838,6 +836,12 @@ export type Props = $ReadOnly<{|
*/
returnKeyType?: ?ReturnKeyType,

/**
* Sets the number of rows for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
*/
rows?: ?number,

/**
* If `true`, the text input obscures the text entered so that sensitive text
* like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'.
Expand Down
18 changes: 14 additions & 4 deletions Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,6 @@ type AndroidProps = $ReadOnly<{|
/**
* Sets the number of lines for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
* @platform android
*/
numberOfLines?: ?number,

Expand All @@ -537,10 +536,14 @@ type AndroidProps = $ReadOnly<{|
/**
* Sets the number of rows for a `TextInput`. Use it with multiline set to
* `true` to be able to fill the lines.
* @platform android
*/
rows?: ?number,

/**
* Sets the maximum number of lines the TextInput can have.
*/
maximumNumberOfLines?: ?number,

/**
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
* Defaults to `true`.
Expand Down Expand Up @@ -1082,6 +1085,12 @@ const emptyFunctionThatReturnsTrue = () => true;
*
*/
function InternalTextInput(props: Props): React.Node {
const {
rows,
numberOfLines,
...otherProps
} = props;

const inputRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);

// Android sends a "onTextChanged" event followed by a "onSelectionChanged" event, for
Expand Down Expand Up @@ -1428,7 +1437,7 @@ function InternalTextInput(props: Props): React.Node {
textInput = (
<RCTTextInputView
ref={_setNativeRef}
{...props}
{...otherProps}
{...eventHandlers}
accessible={accessible}
accessibilityState={_accessibilityState}
Expand All @@ -1437,6 +1446,7 @@ function InternalTextInput(props: Props): React.Node {
dataDetectorTypes={props.dataDetectorTypes}
focusable={focusable}
mostRecentEventCount={mostRecentEventCount}
numberOfLines={props.rows ?? props.numberOfLines}
onBlur={_onBlur}
onKeyPressSync={props.unstable_onKeyPressSync}
onChange={_onChange}
Expand Down Expand Up @@ -1478,7 +1488,7 @@ function InternalTextInput(props: Props): React.Node {
* fixed */
<AndroidTextInput
ref={_setNativeRef}
{...props}
{...otherProps}
{...eventHandlers}
accessible={accessible}
accessibilityState={_accessibilityState}
Expand Down
9 changes: 5 additions & 4 deletions Libraries/Text/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const Text: React.AbstractComponent<
pressRetentionOffset,
role,
suppressHighlighting,
numberOfLines,
...restProps
} = props;

Expand Down Expand Up @@ -196,12 +197,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);
Expand Down Expand Up @@ -233,7 +234,7 @@ const Text: React.AbstractComponent<
isPressable={isPressable}
selectable={_selectable}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
maximumNumberOfLines={numberOfLinesValue}
selectionColor={selectionColor}
style={flattenedStyle}
ref={forwardedRef}
Expand All @@ -259,7 +260,7 @@ const Text: React.AbstractComponent<
ellipsizeMode={ellipsizeMode ?? 'tail'}
isHighlighted={isHighlighted}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
maximumNumberOfLines={numberOfLinesValue}
selectionColor={selectionColor}
style={flattenedStyle}
ref={forwardedRef}
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Text/Text/RCTTextViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ @implementation RCTTextViewManager {

RCT_EXPORT_MODULE(RCTText)

RCT_REMAP_SHADOW_PROPERTY(numberOfLines, maximumNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
RCT_REMAP_SHADOW_PROPERTY(ellipsizeMode, lineBreakMode, NSLineBreakMode)
RCT_REMAP_SHADOW_PROPERTY(adjustsFontSizeToFit, adjustsFontSizeToFit, BOOL)
RCT_REMAP_SHADOW_PROPERTY(minimumFontScale, minimumFontScale, CGFloat)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#import <React/RCTMultilineTextInputView.h>
#import <React/RCTMultilineTextInputViewManager.h>
#import <React/RCTUITextView.h>
#import <React/RCTBaseTextInputShadowView.h>

@implementation RCTMultilineTextInputViewManager

Expand All @@ -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;

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
1 change: 1 addition & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;

- (void)uiManagerWillPerformMounting;
Expand Down
17 changes: 16 additions & 1 deletion Libraries/Text/TextInput/RCTBaseTextInputShadowView.m
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText

- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
NSAttributedString *attributedText = [self measurableAttributedText];
NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy];

/*
* The block below is responsible for setting the exact height of the view in lines
* 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];
_maximumNumberOfLines = self.exactNumberOfLines;
}

if (!_textStorage) {
_textContainer = [NSTextContainer new];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView
RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView];

shadowView.maximumNumberOfLines = 1;
shadowView.exactNumberOfLines = 0;

return shadowView;
}
Expand Down
3 changes: 2 additions & 1 deletion Libraries/Text/TextNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {type TextProps} from './TextProps';

type NativeTextProps = $ReadOnly<{
...TextProps,
maximumNumberOfLines?: ?number,
isHighlighted?: ?boolean,
selectionColor?: ?ProcessedColorValue,
onClick?: ?(event: PressEvent) => mixed,
Expand All @@ -31,7 +32,7 @@ const textViewConfig = {
validAttributes: {
isHighlighted: true,
isPressable: true,
numberOfLines: true,
maximumNumberOfLines: true,
ellipsizeMode: true,
allowFontScaling: true,
dynamicTypeRamp: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public class ViewDefaults {

public static final float FONT_SIZE_SP = 14.0f;
public static final int LINE_HEIGHT = 0;
public static final int NUMBER_OF_LINES = Integer.MAX_VALUE;
public static final int NUMBER_OF_LINES = -1;
public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public class ViewProps {
public static final String LETTER_SPACING = "letterSpacing";
public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing";
public static final String NUMBER_OF_LINES = "numberOfLines";
public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines";
public static final String ELLIPSIZE_MODE = "ellipsizeMode";
public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit";
public static final String MINIMUM_FONT_SCALE = "minimumFontScale";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,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;
Expand Down Expand Up @@ -411,6 +412,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;
markUpdated();
}

@ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN)
public void setLineHeight(float lineHeight) {
mTextAttributes.setLineHeight(lineHeight);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public void setNumberOfLines(ReactTextView view, int numberOfLines) {
view.setNumberOfLines(numberOfLines);
}

// maxLines can only be set in master view (block), doesn't really make sense to set in a span
@ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES)
public void setMaxNumberOfLines(ReactTextView view, int numberOfLines) {
view.setNumberOfLines(numberOfLines);
}

@ReactProp(name = ViewProps.ELLIPSIZE_MODE)
public void setEllipsizeMode(ReactTextView view, @Nullable String ellipsizeMode) {
if (ellipsizeMode == null || ellipsizeMode.equals("tail")) {
Expand Down
Loading