- Reply to issue mrousavy/react-native-vision-camera#2614
- Code review for PR Fix single line text not fully rendered on old architecture
-
-
Save fabOnReact/6a58c713d32ce5bb9c164b6906abac12 to your computer and use it in GitHub Desktop.
Nested rtl short Text with inline image (image shows to the right)
Adding an inline Image adds a TextInlineViewPlaceholderSpan which is responsible for layout calculation. The placeholder does not appear in the Text, but is only used to compute the image dimension and position in the text.
Detailed explanation of this API: react-native-community/discussions-and-proposals#528 (reply in thread)
fabOnReact/react-native@a2285b1 and facebook/react-native@91b3f5d
CLICK TO OPEN TESTS RESULTS
import * as React from 'react';
import {Image, StyleSheet, Text, TextInput, View} from 'react-native';
const fullImage: ImageSource = {
uri: 'https://www.facebook.com/ads/pics/successstories.png',
};
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email = 'مرحبا بالعالم من فابريزيو';
return (
<>
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
writingDirection="rtl"
style={styles.parentText}
numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}}/>
</Text>
</View>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
direction: 'rtl',
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Before | After |
---|---|
Before (changes from this PR): The image placeholderLeftPosition is 345
After (changes from this PR): The image placeholderLeftPosition is 345
Very long rtl text (image does not show and there is no ellipsis)
CLICK TO OPEN TESTS RESULTS
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const fullImage: ImageSource = {
uri: 'https://www.facebook.com/ads/pics/successstories.png',
};
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'مرحبا بالعالم من فابريزيومرحبا بالعالم من فابريزيو مرحبا بالعالم من فابريزيومرحبا بالعالم من فابري زيو';
let textRef = React.useRef(null);
const measureCallback = (x, y, w, h, pageX, pageY) => {
console.warn(
'x: ' +
parseInt(x) +
' y: ' +
parseInt(y) +
' w: ' +
parseInt(w) +
' h: ' +
parseInt(h),
);
};
const measureTextView = () => {
if (textRef) {
textRef.measure(measureCallback);
}
};
return (
<>
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
ref={ref => (textRef = ref)}
writingDirection="rtl"
style={styles.parentText}
numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</View>
<Button title="measure" onPress={measureTextView} />
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
direction: 'rtl',
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Before | After |
---|---|
Text with nested image is shorter than parent view (the image shows and there is no ellipsis)
CLICK TO OPEN TESTS RESULTS
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const fullImage: ImageSource = {
uri: 'https://www.facebook.com/ads/pics/successstories.png',
};
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'مرحبا بالعالم من فابريزيومرحبا بالعالم من فابريزيو مرحبا مرحبا';
let textRef = React.useRef(null);
const measureCallback = (x, y, w, h, pageX, pageY) => {
console.warn(
'x: ' +
parseInt(x) +
' y: ' +
parseInt(y) +
' w: ' +
parseInt(w) +
' h: ' +
parseInt(h),
);
};
const measureTextView = () => {
if (textRef) {
textRef.measure(measureCallback);
}
};
return (
<>
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
ref={ref => (textRef = ref)}
writingDirection="rtl"
style={styles.parentText}
numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</View>
<Button title="measure" onPress={measureTextView} />
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
direction: 'rtl',
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Before | After |
---|---|
const email =
'مرحبا بالعالم من فابريزيومرحبا بالعال';
Before | After |
---|---|
Correctly calculates the placeholderLeftPosition for inline images.
const email =
'مرحبا بالعالم من ';
Before | After |
---|---|
const email = 'مر';
Before | After |
---|---|
const email = 'ر';
Before | After |
---|---|
BoringLayout support for margin and padding with single line text
CLICK TO OPEN TESTS SOURCE CODE
import * as React from 'react';
import {
Button,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
} from 'react-native';
export default function App() {
const email =
'From [email protected] From [email protected]';
return (
<>
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
textBreakStrategy="simple"
numberOfLines={1}
style={styles.input}>
{email}
</Text>
</View>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
flexDirection: 'row',
backgroundColor: 'red',
},
input: {
backgroundColor: 'pink',
marginLeft: 50,
paddingLeft: 50,
marginRight: 50,
paddingRight: 50,
},
});
Paper and Fabric behave the same.
Before | After |
---|---|
The text wraps correctly without leaving white spaces
CLICK TO OPEN SOURCECODE
import * as React from 'react';
import {
Button,
Text,
SafeAreaView,
StyleSheet,
Pressable,
View,
} from 'react-native';
const RNTesterApp = () => {
const [text, setText] = React.useState('From [email protected]');
var things = ['short', 'verylong', 'superlongword'];
var thing = things[Math.floor(Math.random() * things.length)];
const addText = () => {
setText(prevText => prevText + ' ' + thing);
};
const email =
'From [email protected] From [email protected] \n';
return (
<>
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
textBreakStrategy="simple"
adjustFontSizeToFit={true}
style={{backgroundColor: 'pink'}}>
{text}
</Text>
</View>
<Button title="add text" onPress={addText} />
</View>
</>
);
};
export default RNTesterApp;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
flexDirection: 'row',
maxWidth: 300,
},
});
Before | After commit 85308 | After |
---|---|---|
Text width is larger than parent
Text width is higher of parent (YogaMeasureMode.EXACTLY)
CLICK TO OPEN SOURCECODE
import * as React from 'react';
import {
Button,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
} from 'react-native';
export default function App() {
const email =
'From [email protected] From [email protected]';
return (
<>
<View style={styles.flexBrokenStyle}>
<Text textBreakStrategy="simple" numberOfLines={1} style={styles.input}>
{email}
</Text>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
paddingTop: 50,
paddingRight: 0,
width: 300,
height: 300,
backgroundColor: 'red',
},
input: {
backgroundColor: 'pink',
width: 400,
},
});
Before | After |
---|---|
Paper
Before | After |
---|---|
TextView takes the entire line
- TextView onMeasure calculatedWidth is 1080
- Text lineWidth is 406.0
- placeholderLeftPosition is 1080 - 406
CLICK TO OPEN SOURCECODE
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email = 'مرحبا بالعالم من ف';
return (
<>
<Text writingDirection="rtl" style={styles.parentText} numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</>
);
}
const styles = StyleSheet.create({
parentText: {
backgroundColor: 'red',
},
});
Fabric
Before | After |
---|---|
Paper
Before | After |
---|---|
Image does not fix in TextView
CLICK TO OPEN TESTS RESULTS
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'From [email protected] From [email protected]';
return (
<>
<Text style={styles.parentText} numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</>
);
}
const styles = StyleSheet.create({
parentText: {
backgroundColor: 'red',
},
});
Fabric
Before | After |
---|---|
Paper
Before | After |
---|---|
Images do not fit in the line (parent applies flex style)
This is a separate issue caused by the logic that calculates placehoderTop and placeholderLeftPosition, which does not handle scenarios where text is single line. The image is positioned on the second line as if text would wrap on the second line.
CLICK TO OPEN SOURCECODE
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'From [email protected] From [email protected]';
return (
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text style={styles.parentText} numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Fabric
Before | After |
---|---|
Paper
Before | After |
---|---|
Text with images wraps on the second line
CLICK TO OPEN SOURCECODE
Relevant commit 00c318cb742 and https://gist.github.com/assets/24992535/65240c44-ba57-4d66-8dd2-e168aeb98da2
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'From [email protected] ddddddddd dddd dd dd dd dd d';
return (
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text style={styles.parentText}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Fabric:
Before | After |
---|---|
Paper:
Before | After |
---|---|
Text includes emoji (not boring text)
CLICK TO OPEN TESTS RESULTS
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email = 'From vinesdfsd 👍 😊 🐚 😆';
const email2 =
'مرحبا بالعالم من فابريزيومرحبا بالعالم من فابريزيو مرحبا مرحبامن فابريزيو مرحبا مرحبا';
return (
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text numberOfLines={1}>
<Text style={styles.parentText}>{email}</Text>
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
},
flexBrokenStyle: {
// direction: 'rtl',
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
fontSize: 30,
},
});
After |
---|
Veryfing the result of the onLayout callback
CLICK TO OPEN SOURCECODE
import * as React from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
const smallImage = {
uri: 'https://www.facebook.com/favicon.ico',
};
export default function App() {
const email =
'From [email protected] From [email protected]';
const onLayoutCallback = e => {
const {width, height, x, y} = e.nativeEvent.layout;
console.warn(
'w: ' +
parseInt(width) +
' h:' +
parseInt(height) +
' x: ' +
parseInt(x) +
' y: ' +
parseInt(y),
);
};
return (
<View style={styles.container}>
<View style={styles.flexBrokenStyle}>
<Text
onLayout={onLayoutCallback}
style={styles.parentText}
numberOfLines={1}>
<Text>{email}</Text>
<Image source={smallImage} style={{height: 50, width: 50}} />
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 8,
backgroundColor: 'yellow',
},
flexBrokenStyle: {
flexDirection: 'row',
},
parentText: {
backgroundColor: 'red',
},
});
Before (Fabric) | After (Fabric) |
---|---|
Before (Paper) | After (Paper) |
---|---|
Nested Text with different combination of styles (ellipsisMode head)
CLICK TO OPEN TESTS RESULTS