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

[iOS][TextInput]Add numberOfLines and maxNumberOfLines props to TextInput on iOS #38021

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c9e11d5
android part
Szymon20000 Mar 8, 2023
e55eed8
ios changes
Szymon20000 Mar 8, 2023
cd78e0d
update types, configs
Szymon20000 Mar 8, 2023
34acc74
fix NativeTextComponent Props
Szymon20000 Mar 8, 2023
22d7189
fix fabric issue with newlines
Szymon20000 Mar 10, 2023
e5388e1
Apply suggestions from code review
Szymon20000 Mar 15, 2023
448a5eb
add example to rntester
Szymon20000 Mar 15, 2023
8080b34
Merge remote-tracking branch 'upstream/main' into @szymon/numner_of_l…
Szymon20000 Apr 20, 2023
b58d914
fix text component on android - (is was stipping off natest views)
Szymon20000 Apr 22, 2023
b29648d
apply suggestions
Szymon20000 May 16, 2023
036d5ad
Merge remote-tracking branch 'upstream/main' into @szymon/numner_of_l…
Szymon20000 May 16, 2023
24614c6
update rntester examples
Szymon20000 May 16, 2023
2551355
Merge branch 'main' into @szymon/numner_of_lines_rn
Szymon20000 May 29, 2023
e367bc0
Move LayoutMetrics and LayoutPrimitives from core to graphics folder …
sammy-SC May 29, 2023
f77567d
Revert D45904748: Move LayoutMetrics and LayoutPrimitives from core t…
May 29, 2023
4bfc7be
translation auto-update for i18n/fb4a.config.json on master
May 30, 2023
bac869c
Make RNTester use RCTAppDelegate (#37572)
cipolleschi May 30, 2023
cfb5701
Backporting a fix for hermesc on linux (#37596)
cipolleschi May 30, 2023
d5dfc99
Attempt at fixing crash when blurring image on iOS (#37614)
sammy-SC May 30, 2023
f7d9693
Fix Fabric issue with React-Core pod when Xcode project has multiple …
douglowder May 30, 2023
ef6fc1c
Remove `greet.yml` action (#37587)
Pranav-yadav May 30, 2023
39709bb
Add tests in CI not to break Hermes-Xcode integration (#37616)
cipolleschi May 30, 2023
c3e1c41
Use SurfaceRegistry globals whenever available (#37410)
javache May 30, 2023
5a90e0d
Merge remote-tracking ranch 'upstream/main' into @szymon/numner_of_l…
Szymon20000 Jun 14, 2023
908eeca
choose proper numberOfLines prop name based on native config
Szymon20000 Jun 15, 2023
6d108f3
remove android changes in java files
Szymon20000 Jun 22, 2023
ac7cd1f
Merge remote-tracking branch 'upstream/main' into @szymon/number_of_l…
Szymon20000 Jul 8, 2023
c8c662d
fix linting
Szymon20000 Jul 8, 2023
d328d71
fix flow check
Szymon20000 Jul 8, 2023
06a854f
fix linting
Szymon20000 Jul 8, 2023
f39c184
revert weird merge issue
Szymon20000 Jul 8, 2023
ef502b6
fix tests and linting
Szymon20000 Jul 8, 2023
eccd5a1
fix flow again
Szymon20000 Jul 8, 2023
018b0e9
Merge branch 'main' into @szymon/number_of_lines_ios_part
Aug 7, 2023
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 @@ -144,6 +144,8 @@ const RCTTextInputViewConfig = {
placeholder: true,
autoCorrect: true,
multiline: true,
numberOfLines: true,
maximumNumberOfLines: true,
textContentType: true,
maxLength: true,
autoCapitalize: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,12 +374,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 @@ -695,11 +689,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.
*/
maxNumberOfLines?: 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
Original file line number Diff line number Diff line change
Expand Up @@ -366,26 +366,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 @@ -665,6 +651,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.
*/
maxNumberOfLines?: ?number,

/**
* Specifies largest possible scale a font can reach when `allowFontScaling` is enabled.
* Possible values:
Expand All @@ -686,6 +678,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 @@ -847,6 +845,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
13 changes: 11 additions & 2 deletions packages/react-native/Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,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 @@ -426,10 +425,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.
*/
maxNumberOfLines?: ?number,

/**
* When `false`, it will prevent the soft keyboard from showing when the field is focused.
* Defaults to `true`.
Expand Down Expand Up @@ -1102,6 +1105,9 @@ function InternalTextInput(props: Props): React.Node {
accessibilityState,
id,
tabIndex,
rows,
numberOfLines,
maxNumberOfLines,
selection: propsSelection,
...otherProps
} = props;
Expand Down Expand Up @@ -1458,6 +1464,8 @@ function InternalTextInput(props: Props): React.Node {
focusable={tabIndex !== undefined ? !tabIndex : focusable}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
maximumNumberOfLines={maxNumberOfLines}
onBlur={_onBlur}
onKeyPressSync={props.unstable_onKeyPressSync}
onChange={_onChange}
Expand Down Expand Up @@ -1513,6 +1521,7 @@ function InternalTextInput(props: Props): React.Node {
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
maximumNumberOfLines={maxNumberOfLines}
onBlur={_onBlur}
onChange={_onChange}
onFocus={_onFocus}
Expand Down
29 changes: 24 additions & 5 deletions packages/react-native/Libraries/Text/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import flattenStyle from '../StyleSheet/flattenStyle';
import processColor from '../StyleSheet/processColor';
import Platform from '../Utilities/Platform';
import TextAncestor from './TextAncestor';
import {NativeText, NativeVirtualText} from './TextNativeComponent';
import {
CONTAINS_MAX_NUMBER_OF_LINES_RENAME,
NativeText,
NativeVirtualText,
} from './TextNativeComponent';
import * as React from 'react';
import {useContext, useMemo, useState} from 'react';

Expand Down Expand Up @@ -56,6 +60,7 @@ const Text: React.AbstractComponent<
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
numberOfLines,
...restProps
} = props;

Expand Down Expand Up @@ -195,14 +200,29 @@ 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 numberOfLinesProps = useMemo((): {
maximumNumberOfLines?: ?number,
numberOfLines?: ?number,
} => {
if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) {
return {
maximumNumberOfLines: numberOfLinesValue,
};
} else {
return {
numberOfLines: numberOfLinesValue,
};
}
}, [numberOfLinesValue]);

const hasTextAncestor = useContext(TextAncestor);

const _accessible = Platform.select({
Expand Down Expand Up @@ -241,7 +261,6 @@ const Text: React.AbstractComponent<
isHighlighted={isHighlighted}
isPressable={isPressable}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={selectionColor}
Expand All @@ -252,6 +271,7 @@ const Text: React.AbstractComponent<
<NativeText
{...restProps}
{...eventHandlersForText}
{...numberOfLinesProps}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
accessibilityState={nativeTextAccessibilityState}
accessible={
Expand All @@ -264,7 +284,6 @@ const Text: React.AbstractComponent<
ellipsizeMode={ellipsizeMode ?? 'tail'}
isHighlighted={isHighlighted}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={selectionColor}
Expand Down
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
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
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
10 changes: 9 additions & 1 deletion packages/react-native/Libraries/Text/TextNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import {createViewConfig} from '../NativeComponent/ViewConfig';
import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes';
import UIManager from '../ReactNative/UIManager';
import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass';
import {type HostComponent} from '../Renderer/shims/ReactNativeTypes';
Expand All @@ -18,6 +19,7 @@ import {type TextProps} from './TextProps';

type NativeTextProps = $ReadOnly<{
...TextProps,
maximumNumberOfLines?: ?number,
isHighlighted?: ?boolean,
selectionColor?: ?ProcessedColorValue,
onClick?: ?(event: PressEvent) => mixed,
Expand All @@ -31,7 +33,7 @@ const textViewConfig = {
validAttributes: {
isHighlighted: true,
isPressable: true,
numberOfLines: true,
maximumNumberOfLines: true,
ellipsizeMode: true,
allowFontScaling: true,
dynamicTypeRamp: true,
Expand Down Expand Up @@ -73,6 +75,12 @@ export const NativeText: HostComponent<NativeTextProps> =
createViewConfig(textViewConfig),
): any);

const jestIsDefined = typeof jest !== 'undefined';
export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined
? true
: getNativeComponentAttributes('RCTText')?.NativeProps
?.maximumNumberOfLines === 'number';

export const NativeVirtualText: HostComponent<NativeTextProps> =
!global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText')
? NativeText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ namespace facebook::react {

bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const {
return std::tie(
numberOfLines,
maximumNumberOfLines,
ellipsizeMode,
textBreakStrategy,
adjustsFontSizeToFit,
includeFontPadding,
android_hyphenationFrequency) ==
std::tie(
rhs.numberOfLines,
rhs.maximumNumberOfLines,
rhs.ellipsizeMode,
rhs.textBreakStrategy,
Expand All @@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const {
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
return {
debugStringConvertibleItem("numberOfLines", numberOfLines),
debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines),
debugStringConvertibleItem("ellipsizeMode", ellipsizeMode),
debugStringConvertibleItem("textBreakStrategy", textBreakStrategy),
Expand Down
Loading