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

feat(iOS): added lineBreakStrategy attribute to Text/TextInput #31272

Closed
wants to merge 6 commits into from
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
1 change: 1 addition & 0 deletions Libraries/Components/TextInput/RCTTextInputViewConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const RCTTextInputViewConfig = {
clearTextOnFocus: true,
showSoftInputOnFocus: true,
autoFocus: true,
lineBreakStrategyIOS: true,
...ConditionallyIgnoredEventHandlers({
onChange: true,
onSelectionChange: true,
Expand Down
6 changes: 6 additions & 0 deletions Libraries/Components/TextInput/TextInput.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ type IOSProps = $ReadOnly<{|
* @platform ios
*/
textContentType?: ?TextContentType,

/**
* Set line break strategy on iOS.
* @platform ios
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
|}>;

type AndroidProps = $ReadOnly<{|
Expand Down
6 changes: 6 additions & 0 deletions Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ type IOSProps = $ReadOnly<{|
* @platform ios
*/
textContentType?: ?TextContentType,

/**
* Set line break strategy on iOS.
* @platform ios
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
|}>;

type AndroidProps = $ReadOnly<{|
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/BaseText/RCTBaseTextViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ - (RCTShadowView *)shadowView
RCT_REMAP_SHADOW_PROPERTY(lineHeight, textAttributes.lineHeight, CGFloat)
RCT_REMAP_SHADOW_PROPERTY(textAlign, textAttributes.alignment, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(writingDirection, textAttributes.baseWritingDirection, NSWritingDirection)
RCT_REMAP_SHADOW_PROPERTY(lineBreakStrategyIOS, textAttributes.lineBreakStrategy, NSLineBreakStrategy)
// 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 Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extern NSString *const RCTTextAttributesTagAttributeName;
@property (nonatomic, assign) CGFloat lineHeight;
@property (nonatomic, assign) NSTextAlignment alignment;
@property (nonatomic, assign) NSWritingDirection baseWritingDirection;
@property (nonatomic, assign) NSLineBreakStrategy lineBreakStrategy;
// Decoration
@property (nonatomic, strong, nullable) UIColor *textDecorationColor;
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
Expand Down
11 changes: 10 additions & 1 deletion Libraries/Text/RCTTextAttributes.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ - (instancetype)init
_maxFontSizeMultiplier = NAN;
_alignment = NSTextAlignmentNatural;
_baseWritingDirection = NSWritingDirectionNatural;
_lineBreakStrategy = NSLineBreakStrategyNone;
_textShadowRadius = NAN;
_opacity = NAN;
_textTransform = RCTTextTransformUndefined;
Expand Down Expand Up @@ -66,6 +67,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural
? textAttributes->_baseWritingDirection
: _baseWritingDirection; // *
_lineBreakStrategy = textAttributes->_lineBreakStrategy ?: _lineBreakStrategy;

// Decoration
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
Expand Down Expand Up @@ -117,6 +119,13 @@ - (NSParagraphStyle *)effectiveParagraphStyle
isParagraphStyleUsed = YES;
}

if (_lineBreakStrategy != NSLineBreakStrategyNone) {
if (@available(iOS 14.0, *)) {
paragraphStyle.lineBreakStrategy = _lineBreakStrategy;
isParagraphStyleUsed = YES;
}
}

if (!isnan(_lineHeight)) {
CGFloat lineHeight = _lineHeight * self.effectiveFontSizeMultiplier;
paragraphStyle.minimumLineHeight = lineHeight;
Expand Down Expand Up @@ -318,7 +327,7 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes
RCTTextAttributesCompareFloats(_letterSpacing) &&
// Paragraph Styles
RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) &&
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) &&
// Decoration
RCTTextAttributesCompareObjects(_textDecorationColor) && RCTTextAttributesCompareOthers(_textDecorationStyle) &&
RCTTextAttributesCompareOthers(_textDecorationLine) &&
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/TextNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const textViewConfig = {
onInlineViewLayout: true,
dataDetectorType: true,
android_hyphenationFrequency: true,
lineBreakStrategyIOS: true,
},
directEventTypes: {
topTextLayout: {
Expand Down
7 changes: 7 additions & 0 deletions Libraries/Text/TextProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,11 @@ export type TextProps = $ReadOnly<{|
* See https://reactnative.dev/docs/text#supperhighlighting
*/
suppressHighlighting?: ?boolean,

/**
* Set line break strategy on iOS.
*
* See https://reactnative.dev/docs/text.html#linebreakstrategyios
*/
lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'),
|}>;
1 change: 1 addition & 0 deletions React/Base/RCTConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ typedef NSURL RCTFileURL;
+ (NSTextAlignment)NSTextAlignment:(id)json;
+ (NSUnderlineStyle)NSUnderlineStyle:(id)json;
+ (NSWritingDirection)NSWritingDirection:(id)json;
+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json;
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json;
Expand Down
19 changes: 19 additions & 0 deletions React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,25 @@ + (NSLocale *)NSLocale:(id)json
NSWritingDirectionNatural,
integerValue)

+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json RCT_DYNAMIC
{
if (@available(iOS 14.0, *)) {
static NSDictionary *mapping;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mapping = @{
@"none" : @(NSLineBreakStrategyNone),
@"standard" : @(NSLineBreakStrategyStandard),
@"hangul-word" : @(NSLineBreakStrategyHangulWordPriority),
@"push-out": @(NSLineBreakStrategyPushOut)
};
});
return RCTConvertEnumValue("NSLineBreakStrategy", mapping, @(NSLineBreakStrategyNone), json).integerValue;
} else {
return NSLineBreakStrategyNone;
}
}

RCT_ENUM_CONVERTER(
UITextAutocapitalizationType,
(@{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
baseWritingDirection = textAttributes.baseWritingDirection.has_value()
? textAttributes.baseWritingDirection
: baseWritingDirection;
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
? textAttributes.lineBreakStrategy
: lineBreakStrategy;

// Decoration
textDecorationColor = textAttributes.textDecorationColor
Expand Down Expand Up @@ -110,6 +113,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
allowFontScaling,
alignment,
baseWritingDirection,
lineBreakStrategy,
textDecorationColor,
textDecorationLineType,
textDecorationStyle,
Expand All @@ -129,6 +133,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
rhs.allowFontScaling,
rhs.alignment,
rhs.baseWritingDirection,
rhs.lineBreakStrategy,
rhs.textDecorationColor,
rhs.textDecorationLineType,
rhs.textDecorationStyle,
Expand Down Expand Up @@ -187,6 +192,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
debugStringConvertibleItem("lineHeight", lineHeight),
debugStringConvertibleItem("alignment", alignment),
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),

// Decoration
debugStringConvertibleItem("textDecorationColor", textDecorationColor),
Expand Down
2 changes: 2 additions & 0 deletions ReactCommon/react/renderer/attributedstring/TextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class TextAttributes : public DebugStringConvertible {
Float lineHeight{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextAlignment> alignment{};
std::optional<WritingDirection> baseWritingDirection{};
std::optional<LineBreakStrategy> lineBreakStrategy{};

// Decoration
SharedColor textDecorationColor{};
Expand Down Expand Up @@ -121,6 +122,7 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.lineHeight,
textAttributes.alignment,
textAttributes.baseWritingDirection,
textAttributes.lineBreakStrategy,
textAttributes.textDecorationColor,
textAttributes.textDecorationLineType,
textAttributes.textDecorationStyle,
Expand Down
57 changes: 57 additions & 0 deletions ReactCommon/react/renderer/attributedstring/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,53 @@ inline std::string toString(const WritingDirection &writingDirection) {
return "auto";
}


inline void fromRawValue(
const PropsParserContext &context,
const RawValue &value,
LineBreakStrategy &result) {
react_native_assert(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "none") {
result = LineBreakStrategy::None;
} else if (string == "push-out") {
result = LineBreakStrategy::PushOut;
} else if (string == "hangul-word") {
result = LineBreakStrategy::HangulWordPriority;
} else if (string == "standard") {
result = LineBreakStrategy::Standard;
} else {
LOG(ERROR) << "Unsupported LineBreakStrategy value: " << string;
react_native_assert(false);
// sane default for prod
result = LineBreakStrategy::None;
}
return;
}

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

inline std::string toString(const LineBreakStrategy &lineBreakStrategy) {
switch (lineBreakStrategy) {
case LineBreakStrategy::None:
return "none";
case LineBreakStrategy::PushOut:
return "push-out";
case LineBreakStrategy::HangulWordPriority:
return "hangul-word";
case LineBreakStrategy::Standard:
return "standard";
}

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

inline void fromRawValue(
const PropsParserContext &context,
const RawValue &value,
Expand Down Expand Up @@ -873,6 +920,10 @@ inline folly::dynamic toDynamic(const TextAttributes &textAttributes) {
_textAttributes(
"baseWritingDirection", toString(*textAttributes.baseWritingDirection));
}
if (textAttributes.lineBreakStrategy.has_value()) {
_textAttributes(
"lineBreakStrategyIOS", toString(*textAttributes.lineBreakStrategy));
}
// Decoration
if (textAttributes.textDecorationColor) {
_textAttributes(
Expand Down Expand Up @@ -982,6 +1033,7 @@ constexpr static MapBuffer::Key TA_KEY_TEXT_SHADOW_COLOR = 19;
constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 20;
constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 21;
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 22;
constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 23;

// constants for ParagraphAttributes serialization
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
Expand Down Expand Up @@ -1084,6 +1136,11 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) {
TA_KEY_BEST_WRITING_DIRECTION,
toString(*textAttributes.baseWritingDirection));
}
if (textAttributes.lineBreakStrategy.has_value()) {
builder.putString(
TA_KEY_LINE_BREAK_STRATEGY,
toString(*textAttributes.lineBreakStrategy));
}
// Decoration
if (textAttributes.textDecorationColor) {
builder.putInt(
Expand Down
7 changes: 7 additions & 0 deletions ReactCommon/react/renderer/attributedstring/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ enum class WritingDirection {
RightToLeft // Right to left writing direction.
};

enum class LineBreakStrategy {
None, // Don't use any line break strategies
PushOut, // Use the push out line break strategy.
HangulWordPriority, // When specified, it prohibits breaking between Hangul characters.
Standard // Use the same configuration of line break strategies that the system uses for standard UI labels.
};

enum class TextDecorationLineType {
None,
Underline,
Expand Down
12 changes: 12 additions & 0 deletions ReactCommon/react/renderer/components/text/BaseTextProps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ static TextAttributes convertRawProp(
"baseWritingDirection",
sourceTextAttributes.baseWritingDirection,
defaultTextAttributes.baseWritingDirection);
textAttributes.lineBreakStrategy = convertRawProp(
context,
rawProps,
"lineBreakStrategyIOS",
sourceTextAttributes.lineBreakStrategy,
defaultTextAttributes.lineBreakStrategy);

// Decoration
textAttributes.textDecorationColor = convertRawProp(
Expand Down Expand Up @@ -243,6 +249,12 @@ void BaseTextProps::setProp(
textAttributes,
baseWritingDirection,
"baseWritingDirection");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
textAttributes,
lineBreakStrategy,
"lineBreakStrategyIOS");
REBUILD_FIELD_SWITCH_CASE(
defaults,
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value());
isParagraphStyleUsed = YES;
}

if (textAttributes.lineBreakStrategy.has_value()) {
paragraphStyle.lineBreakStrategy =
RCTNSLineBreakStrategyFromLineBreakStrategy(textAttributes.lineBreakStrategy.value());
isParagraphStyleUsed = YES;
}

if (!isnan(textAttributes.lineHeight)) {
CGFloat lineHeight = textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ inline static NSWritingDirection RCTNSWritingDirectionFromWritingDirection(Writi
}
}

inline static NSLineBreakStrategy RCTNSLineBreakStrategyFromLineBreakStrategy(LineBreakStrategy lineBreakStrategy)
{
switch (lineBreakStrategy) {
case LineBreakStrategy::None:
return NSLineBreakStrategyNone;
case LineBreakStrategy::PushOut:
return NSLineBreakStrategyPushOut;
case LineBreakStrategy::HangulWordPriority:
if (@available(iOS 14.0, *)) {
return NSLineBreakStrategyHangulWordPriority;
} else {
return NSLineBreakStrategyNone;
}
case LineBreakStrategy::Standard:
if (@available(iOS 14.0, *)) {
return NSLineBreakStrategyStandard;
} else {
return NSLineBreakStrategyNone;
}
}
}

inline static RCTFontStyle RCTFontStyleFromFontStyle(FontStyle fontStyle)
{
switch (fontStyle) {
Expand Down
Loading