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

fix(ios): line break mode property added for iOS #45968

Closed
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 @@ -144,6 +144,7 @@ const RCTTextInputViewConfig = {
showSoftInputOnFocus: true,
autoFocus: true,
lineBreakStrategyIOS: true,
lineBreakModeIOS: true,
smartInsertDelete: true,
...ConditionallyIgnoredEventHandlers({
onChange: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,19 @@ export interface TextInputIOSProps {
| 'push-out'
| undefined;

/**
* Set line break mode on iOS.
* @platform ios
*/
lineBreakModeIOS?:
| 'wordWrapping'
| 'char'
| 'clip'
| 'head'
| 'middle'
| 'tail'
| undefined;

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,19 @@ type IOSProps = $ReadOnly<{|
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),

/**
* Set line break mode on iOS.
* @platform ios
*/
lineBreakModeIOS?: ?(
| 'wordWrapping'
| 'char'
| 'clip'
| 'head'
| 'middle'
| 'tail'
),

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
Expand Down
13 changes: 13 additions & 0 deletions packages/react-native/Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,19 @@ type IOSProps = $ReadOnly<{|
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),

/**
* Set line break mode on iOS.
* @platform ios
*/
lineBreakModeIOS?: ?(
| 'wordWrapping'
| 'char'
| 'clip'
| 'head'
| 'middle'
| 'tail'
),

/**
* If `false`, the iOS system will not insert an extra space after a paste operation
* neither delete one or two spaces after a cut or delete operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ - (RCTShadowView *)shadowView
RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection)
RCT_REMAP_SHADOW_PROPERTY(lineBreakStrategyIOS, textAttributes.lineBreakStrategy, NSLineBreakStrategy)
RCT_REMAP_SHADOW_PROPERTY(lineBreakModeIOS, textAttributes.lineBreakMode, NSLineBreakMode)
// Decoration
RCT_REMAP_SHADOW_PROPERTY(textDecorationColor, textAttributes.textDecorationColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(textDecorationStyle, textAttributes.textDecorationStyle, NSUnderlineStyle)
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
@property (nonatomic, assign) NSTextAlignment alignment;
@property (nonatomic, assign) NSWritingDirection baseWritingDirection;
@property (nonatomic, assign) NSLineBreakStrategy lineBreakStrategy;
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
// Decoration
@property (nonatomic, strong, nullable) UIColor *textDecorationColor;
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native/Libraries/Text/RCTTextAttributes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ - (instancetype)init
_alignment = NSTextAlignmentNatural;
_baseWritingDirection = NSWritingDirectionNatural;
_lineBreakStrategy = NSLineBreakStrategyNone;
_lineBreakMode = NSLineBreakByWordWrapping;
_textShadowRadius = NAN;
_opacity = NAN;
_textTransform = RCTTextTransformUndefined;
Expand Down Expand Up @@ -70,6 +71,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
? textAttributes->_baseWritingDirection
: _baseWritingDirection; // *
_lineBreakStrategy = textAttributes->_lineBreakStrategy ?: _lineBreakStrategy;
_lineBreakMode = textAttributes->_lineBreakMode ?: _lineBreakMode;

// Decoration
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
Expand Down Expand Up @@ -128,6 +130,11 @@ - (NSParagraphStyle *)effectiveParagraphStyle
}
}

if (_lineBreakMode != NSLineBreakByWordWrapping) {
paragraphStyle.lineBreakMode = _lineBreakMode;
isParagraphStyleUsed = YES;
}

if (!isnan(_lineHeight)) {
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
paragraphStyle.minimumLineHeight = lineHeight;
Expand Down Expand Up @@ -336,6 +343,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes
// Paragraph Styles
RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) &&
RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) &&
RCTTextAttributesCompareOthers(_lineBreakMode) &&
// Decoration
RCTTextAttributesCompareObjects(_textDecorationColor) && RCTTextAttributesCompareOthers(_textDecorationStyle) &&
RCTTextAttributesCompareOthers(_textDecorationLine) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2593,6 +2593,14 @@ type IOSProps = $ReadOnly<{|
spellCheck?: ?boolean,
textContentType?: ?TextContentType,
lineBreakStrategyIOS?: ?(\\"none\\" | \\"standard\\" | \\"hangul-word\\" | \\"push-out\\"),
lineBreakModeIOS?: ?(
| \\"wordWrapping\\"
| \\"char\\"
| \\"clip\\"
| \\"head\\"
| \\"middle\\"
| \\"tail\\"
),
smartInsertDelete?: ?boolean,
|}>;
type AndroidProps = $ReadOnly<{|
Expand Down Expand Up @@ -2938,6 +2946,14 @@ type IOSProps = $ReadOnly<{|
spellCheck?: ?boolean,
textContentType?: ?TextContentType,
lineBreakStrategyIOS?: ?(\\"none\\" | \\"standard\\" | \\"hangul-word\\" | \\"push-out\\"),
lineBreakModeIOS?: ?(
| \\"wordWrapping\\"
| \\"char\\"
| \\"clip\\"
| \\"head\\"
| \\"middle\\"
| \\"tail\\"
),
smartInsertDelete?: ?boolean,
|}>;
type AndroidProps = $ReadOnly<{|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
? textAttributes.lineBreakStrategy
: lineBreakStrategy;
lineBreakMode = textAttributes.lineBreakMode.has_value()
? textAttributes.lineBreakMode
: lineBreakMode;

// Decoration
textDecorationColor = textAttributes.textDecorationColor
Expand Down Expand Up @@ -216,6 +219,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
debugStringConvertibleItem("alignment", alignment),
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),
debugStringConvertibleItem("lineBreakModeIOS", lineBreakMode),

// Decoration
debugStringConvertibleItem("textDecorationColor", textDecorationColor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class TextAttributes : public DebugStringConvertible {
std::optional<TextAlignment> alignment{};
std::optional<WritingDirection> baseWritingDirection{};
std::optional<LineBreakStrategy> lineBreakStrategy{};
std::optional<LineBreakMode> lineBreakMode{};

// Decoration
SharedColor textDecorationColor{};
Expand Down Expand Up @@ -128,6 +129,7 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.textAlignVertical,
textAttributes.baseWritingDirection,
textAttributes.lineBreakStrategy,
textAttributes.lineBreakMode,
textAttributes.textDecorationColor,
textAttributes.textDecorationLineType,
textAttributes.textDecorationStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,60 @@ inline std::string toString(const LineBreakStrategy& lineBreakStrategy) {
return "none";
}

inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
LineBreakMode& result) {
react_native_expect(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "wordWrapping") {
result = LineBreakMode::Word;
} else if (string == "char") {
result = LineBreakMode::Char;
} else if (string == "clip") {
result = LineBreakMode::Clip;
} else if (string == "head") {
result = LineBreakMode::Head;
} else if (string == "middle") {
result = LineBreakMode::Middle;
} else if (string == "tail") {
result = LineBreakMode::Tail;
} else {
LOG(ERROR) << "Unsupported LineBreakStrategy value: " << string;
react_native_expect(false);
// sane default for prod
result = LineBreakMode::Word;
}
return;
}

LOG(ERROR) << "Unsupported LineBreakStrategy type";
// sane default for prod
result = LineBreakMode::Tail;
}

inline std::string toString(const LineBreakMode& lineBreakMode) {
switch (lineBreakMode) {
case LineBreakMode::Word:
return "word";
case LineBreakMode::Char:
return "char";
case LineBreakMode::Clip:
return "clip";
case LineBreakMode::Head:
return "head";
case LineBreakMode::Middle:
return "middle";
case LineBreakMode::Tail:
return "tail";
}

LOG(ERROR) << "Unsupported LineBreakStrategy value";
// sane default for prod
return "word";
}

inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ enum class LineBreakStrategy {
// system uses for standard UI labels.
};

enum class LineBreakMode {
Word, // Wrap at word boundaries, default
Char, // Wrap at character boundaries
Clip, // Simply clip
Head, // Truncate at head of line: "...wxyz"
Middle, // Truncate middle of line: "ab...yz"
Tail // Truncate at tail of line: "abcd..."
};

enum class TextDecorationLineType {
None,
Underline,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ static TextAttributes convertRawProp(
"lineBreakStrategyIOS",
sourceTextAttributes.lineBreakStrategy,
defaultTextAttributes.lineBreakStrategy);
textAttributes.lineBreakMode = convertRawProp(
context,
rawProps,
"lineBreakModeIOS",
sourceTextAttributes.lineBreakMode,
defaultTextAttributes.lineBreakMode);

// Decoration
textAttributes.textDecorationColor = convertRawProp(
Expand Down Expand Up @@ -286,6 +292,12 @@ void BaseTextProps::setProp(
textAttributes,
lineBreakStrategy,
"lineBreakStrategyIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
lineBreakMode,
"lineBreakModeIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}

if (textAttributes.lineBreakMode.has_value()) {
paragraphStyle.lineBreakMode =
RCTNSLineBreakModeFromLineBreakMode(textAttributes.lineBreakMode.value());
isParagraphStyleUsed = YES;
}

if (isParagraphStyleUsed) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ inline static NSLineBreakStrategy RCTNSLineBreakStrategyFromLineBreakStrategy(
}
}

inline static NSLineBreakMode RCTNSLineBreakModeFromLineBreakMode(
facebook::react::LineBreakMode lineBreakMode)
{
switch (lineBreakMode) {
case facebook::react::LineBreakMode::Word:
return NSLineBreakByWordWrapping;
case facebook::react::LineBreakMode::Char:
return NSLineBreakByCharWrapping;
case facebook::react::LineBreakMode::Clip:
return NSLineBreakByClipping;
case facebook::react::LineBreakMode::Head:
return NSLineBreakByTruncatingHead;
case facebook::react::LineBreakMode::Middle:
return NSLineBreakByTruncatingMiddle;
case facebook::react::LineBreakMode::Tail:
return NSLineBreakByTruncatingTail;
}
}

inline static RCTFontStyle RCTFontStyleFromFontStyle(facebook::react::FontStyle fontStyle)
{
switch (fontStyle) {
Expand Down
42 changes: 42 additions & 0 deletions packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,48 @@ const textInputExamples: Array<RNTesterModuleExample> = [
);
},
},
{
title: 'Line Break Mode',
render: function (): React.Node {
const lineBreakMode = [
'wordWrapping',
'char',
'clip',
'head',
'middle',
'tail',
];
const textByCode = {
en: 'verylongtext-dummydummydummydummydummydummydummydummydummydummydummydummy',
ko: '한글개행한글개행-한글개행한글개행한글개행한글개행한글개행한글개행한글개행한글개행한글개행한글개행',
};
return (
<View>
{lineBreakMode.map(strategy => {
return (
<View key={strategy} style={{marginBottom: 12}}>
<Text
style={{
backgroundColor: 'lightgrey',
}}>{`Mode: ${strategy}`}</Text>
{Object.keys(textByCode).map(code => {
return (
<View key={code}>
<Text style={{fontWeight: 'bold'}}>{`[${code}]`}</Text>
<ExampleTextInput
lineBreakModeIOS={strategy}
defaultValue={textByCode[code]}
/>
</View>
);
})}
</View>
);
})}
</View>
);
},
},
];

module.exports = ({
Expand Down
Loading