mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat(iOS): added lineBreakStrategy attribute to Text/TextInput (#31272)
Summary: iOS did not support the implementation of Korean word-wrap(line-break) before iOS14. If the attribute applied, the word-wrap of Korean will works. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Added] - Line break strategy for Text and TextInput components Pull Request resolved: https://github.com/facebook/react-native/pull/31272 Test Plan: 1. Test build and run on above iOS 14. 2. Test it does not affect existing text components when set default(none) strategy. 3. Test whether word-wrap works with Korean when set hangul-word strategy. <img src="https://user-images.githubusercontent.com/26326015/112963967-d7f70c00-9182-11eb-9a34-8c758b80c219.png" width="300" height="" style="max-width:100%;"> Reviewed By: javache Differential Revision: D39824809 Pulled By: lunaleaps fbshipit-source-id: 42cb0385221a38c84e80d3494d1bfc1934ecf32b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a0ee6fae5e
commit
048194849b
@@ -149,6 +149,7 @@ const RCTTextInputViewConfig = {
|
||||
clearTextOnFocus: true,
|
||||
showSoftInputOnFocus: true,
|
||||
autoFocus: true,
|
||||
lineBreakStrategyIOS: true,
|
||||
...ConditionallyIgnoredEventHandlers({
|
||||
onChange: true,
|
||||
onSelectionChange: true,
|
||||
|
||||
@@ -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<{|
|
||||
|
||||
@@ -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<{|
|
||||
|
||||
@@ -42,6 +42,7 @@ RCT_REMAP_SHADOW_PROPERTY(letterSpacing, textAttributes.letterSpacing, CGFloat)
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -27,6 +27,7 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib
|
||||
_maxFontSizeMultiplier = NAN;
|
||||
_alignment = NSTextAlignmentNatural;
|
||||
_baseWritingDirection = NSWritingDirectionNatural;
|
||||
_lineBreakStrategy = NSLineBreakStrategyNone;
|
||||
_textShadowRadius = NAN;
|
||||
_opacity = NAN;
|
||||
_textTransform = RCTTextTransformUndefined;
|
||||
@@ -66,6 +67,7 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib
|
||||
_baseWritingDirection = textAttributes->_baseWritingDirection != NSWritingDirectionNatural
|
||||
? textAttributes->_baseWritingDirection
|
||||
: _baseWritingDirection; // *
|
||||
_lineBreakStrategy = textAttributes->_lineBreakStrategy ?: _lineBreakStrategy;
|
||||
|
||||
// Decoration
|
||||
_textDecorationColor = textAttributes->_textDecorationColor ?: _textDecorationColor;
|
||||
@@ -117,6 +119,13 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib
|
||||
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;
|
||||
@@ -318,7 +327,7 @@ static NSString *capitalizeText(NSString *text)
|
||||
RCTTextAttributesCompareFloats(_letterSpacing) &&
|
||||
// Paragraph Styles
|
||||
RCTTextAttributesCompareFloats(_lineHeight) && RCTTextAttributesCompareFloats(_alignment) &&
|
||||
RCTTextAttributesCompareOthers(_baseWritingDirection) &&
|
||||
RCTTextAttributesCompareOthers(_baseWritingDirection) && RCTTextAttributesCompareOthers(_lineBreakStrategy) &&
|
||||
// Decoration
|
||||
RCTTextAttributesCompareObjects(_textDecorationColor) && RCTTextAttributesCompareOthers(_textDecorationStyle) &&
|
||||
RCTTextAttributesCompareOthers(_textDecorationLine) &&
|
||||
|
||||
@@ -45,6 +45,7 @@ const textViewConfig = {
|
||||
onInlineViewLayout: true,
|
||||
dataDetectorType: true,
|
||||
android_hyphenationFrequency: true,
|
||||
lineBreakStrategyIOS: true,
|
||||
},
|
||||
directEventTypes: {
|
||||
topTextLayout: {
|
||||
|
||||
@@ -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'),
|
||||
|}>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -375,6 +375,25 @@ RCT_ENUM_CONVERTER(
|
||||
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,
|
||||
(@{
|
||||
|
||||
@@ -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
|
||||
@@ -110,6 +113,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
|
||||
allowFontScaling,
|
||||
alignment,
|
||||
baseWritingDirection,
|
||||
lineBreakStrategy,
|
||||
textDecorationColor,
|
||||
textDecorationLineType,
|
||||
textDecorationStyle,
|
||||
@@ -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,
|
||||
@@ -187,6 +192,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
|
||||
debugStringConvertibleItem("lineHeight", lineHeight),
|
||||
debugStringConvertibleItem("alignment", alignment),
|
||||
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
|
||||
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),
|
||||
|
||||
// Decoration
|
||||
debugStringConvertibleItem("textDecorationColor", textDecorationColor),
|
||||
|
||||
@@ -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{};
|
||||
@@ -121,6 +122,7 @@ struct hash<facebook::react::TextAttributes> {
|
||||
textAttributes.lineHeight,
|
||||
textAttributes.alignment,
|
||||
textAttributes.baseWritingDirection,
|
||||
textAttributes.lineBreakStrategy,
|
||||
textAttributes.textDecorationColor,
|
||||
textAttributes.textDecorationLineType,
|
||||
textAttributes.textDecorationStyle,
|
||||
|
||||
@@ -420,6 +420,52 @@ 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,
|
||||
@@ -873,6 +919,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(
|
||||
@@ -982,6 +1032,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;
|
||||
@@ -1084,6 +1135,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(
|
||||
|
||||
@@ -74,6 +74,15 @@ 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,
|
||||
|
||||
@@ -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(
|
||||
@@ -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,
|
||||
|
||||
@@ -156,6 +156,12 @@ NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(T
|
||||
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);
|
||||
paragraphStyle.minimumLineHeight = lineHeight;
|
||||
|
||||
+22
@@ -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) {
|
||||
|
||||
@@ -1246,4 +1246,41 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Line Break Strategy',
|
||||
render: function (): React.Node {
|
||||
const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out'];
|
||||
const textByCode = {
|
||||
en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy',
|
||||
ko: '한글개행 한글개행 한글개행 한글개행 한글개행 한글개행 한글개행 한글개행',
|
||||
ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう',
|
||||
cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行',
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{lineBreakStrategy.map(strategy => {
|
||||
return (
|
||||
<View key={strategy} style={{marginBottom: 12}}>
|
||||
<Text
|
||||
style={{
|
||||
backgroundColor: 'lightgrey',
|
||||
}}>{`Strategy: ${strategy}`}</Text>
|
||||
{Object.keys(textByCode).map(code => {
|
||||
return (
|
||||
<View key={code}>
|
||||
<Text style={{fontWeight: 'bold'}}>{`[${code}]`}</Text>
|
||||
<Text lineBreakStrategyIOS={strategy}>
|
||||
{textByCode[code]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -862,4 +862,43 @@ exports.examples = ([
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Line Break Strategy',
|
||||
render: function (): React.Node {
|
||||
const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out'];
|
||||
const textByCode = {
|
||||
en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy',
|
||||
ko: '한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행',
|
||||
ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう',
|
||||
cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行',
|
||||
};
|
||||
return (
|
||||
<View>
|
||||
{lineBreakStrategy.map(strategy => {
|
||||
return (
|
||||
<View key={strategy} style={{marginBottom: 12}}>
|
||||
<Text
|
||||
style={{
|
||||
backgroundColor: 'lightgrey',
|
||||
}}>{`Strategy: ${strategy}`}</Text>
|
||||
{Object.keys(textByCode).map(code => {
|
||||
return (
|
||||
<View key={code}>
|
||||
<Text style={{fontWeight: 'bold'}}>{`[${code}]`}</Text>
|
||||
<TextInput
|
||||
multiline
|
||||
lineBreakStrategyIOS={strategy}
|
||||
style={styles.default}
|
||||
defaultValue={textByCode[code]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
]: Array<RNTesterModuleExample>);
|
||||
|
||||
Reference in New Issue
Block a user