[ReText, animatedProps text] Incorrect behavior with numeric text in fitting containers. Some numbers end with an ellipsis, IOS only
gendalf-thug opened this issue · 4 comments
gendalf-thug commented
Description
Bug demo in my project: https://drive.google.com/file/d/1dP7aYLf2zNKQvo8odrSgdm9GzwNU-AqO/view?usp=share_link
Steps to reproduce
- Setup latest blank expo project(SDK 52) with reanimated 3.16.2
- Write this component or use ReText from 'react-native-redash' library
import {memo} from 'react'
import {StyleSheet, TextInput} from 'react-native'
import Animated, {
SharedValue,
AnimatedProps,
useAnimatedProps,
} from 'react-native-reanimated'
import {Color} from 'src/themes'
import {IS_ANDROID} from 'src/variables'
import type {TextInputProps, TextProps as RNTextProps} from 'react-native'
interface TextProps extends Omit<TextInputProps, 'value' | 'style'> {
text: SharedValue<string> | SharedValue<number>
style?: AnimatedProps<RNTextProps>['style']
}
Animated.addWhitelistedNativeProps({text: true})
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
export const AnimText = memo((props: TextProps) => {
const {style, text, ...rest} = props
const animatedProps = useAnimatedProps(() => {
return {
text: String(text.value),
} as any
})
return (
<AnimatedTextInput
underlineColorAndroid="transparent"
editable={false}
value={String(text.value)}
style={[styles.baseStyle, style || undefined]}
{...rest}
animatedProps={animatedProps}
/>
)
})
const styles = StyleSheet.create({
baseStyle: {
color: Color.textPrimary,
padding: IS_ANDROID ? 0 : undefined,
},
})
- Component
import {useEffect} from 'react'
import {View} from 'react-native'
import {useDerivedValue, useSharedValue} from 'react-native-reanimated'
import {AnimText} from 'ui/AnimText'
const formatSecondsTimerWorklet = (sec_num: number) => {
'worklet'
let minutes, seconds, hours
hours = Math.floor(sec_num / 3600)
minutes = Math.floor((sec_num - hours * 3600) / 60)
seconds = sec_num - hours * 3600 - minutes * 60
if (minutes < 10) {
minutes = '0' + minutes
}
if (seconds < 10) {
seconds = '0' + seconds
}
if (hours < 10) {
hours = '0' + hours
}
return hours + ':' + minutes + ':' + seconds
}
export default function App() {
const timer = useSharedValue(1)
const timerText = useDerivedValue(() => {
return formatSecondsTimerWorklet(timer.value)
}, [])
useEffect(() => {
const interval = setInterval(() => {
timer.value += 1
}, 1000)
return () => {
clearInterval(interval)
}
}, [])
return (
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
<AnimText style={{fontSize: 50}} text={timerText} defaultValue="00:00" />
</View>
)
}
Snack or a link to a repository
Reanimated version
3.16.2
React Native version
0.76.3
Platforms
iOS
JavaScript runtime
None
Workflow
Expo Go
Architecture
None
Build type
None
Device
None
Device model
No response
Acknowledgements
Yes
github-actions commented
Hey! 👋
The issue doesn't seem to contain a minimal reproduction.
Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?
github-actions commented
Hey! 👋
It looks like you've omitted a few important sections from the issue template.
Please complete Snack or a link to a repository section.
gendalf-thug commented
Help? Feedback? Human’s comment?
dhruv-00 commented
Facing Same issue
Here is minimal Reproducible
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedProps,
runOnJS,
} from 'react-native-reanimated';
const SLIDER_WIDTH = 300;
Animated.addWhitelistedNativeProps({ text: true });
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
interface RangePickerProps {
initialValue?: number; // in meters
maxValue?: number; // in meters
unit?: string;
onValueChange?: (value: number) => void;
}
const theme = {
colors: {
primary: '#b58df1',
},
};
const RangePicker: React.FC<RangePickerProps> = ({
initialValue = 0,
maxValue = 100,
unit = 'm',
onValueChange,
}) => {
const SLIDER_PADDING = 5;
const HANDLE_WIDTH = 40;
const USABLE_WIDTH = SLIDER_WIDTH - HANDLE_WIDTH - SLIDER_PADDING * 2;
// Convert initial value to slider position
const initialOffset = (initialValue / maxValue) * USABLE_WIDTH;
const offset = useSharedValue(initialOffset);
const currentValue = useSharedValue(initialValue);
const pan = Gesture.Pan().onChange((event) => {
const newOffset = Math.max(
0,
Math.min(USABLE_WIDTH, offset.value + event.changeX)
);
offset.value = newOffset;
// Calculate value with better precision
const rawValue = (newOffset / USABLE_WIDTH) * maxValue;
const newValue = Math.round(rawValue);
currentValue.value = newValue;
onValueChange && runOnJS(onValueChange)(newValue);
});
const sliderStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
const animatedProps = useAnimatedProps(() => {
const displayValue = currentValue.value.toFixed(0);
return {
text: displayValue.toString(),
defaultValue: displayValue.toString(),
};
});
return (
<View style={styles.container}>
<View style={styles.rangePickerContainer}>
<AnimatedTextInput
animatedProps={animatedProps}
style={[styles.boxWidthText, { color: theme.colors.primary }]}
editable={false}
/>
<Text style={[styles.boxWidthText, { color: theme.colors.primary }]}>
{' ' + unit}
</Text>
</View>
<View style={styles.sliderTrack}>
<GestureDetector gesture={pan}>
<Animated.View style={[styles.sliderHandle, sliderStyle]} />
</GestureDetector>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 32,
},
sliderTrack: {
width: SLIDER_WIDTH,
height: 30,
backgroundColor: theme.colors.primary,
borderRadius: 25,
justifyContent: 'center',
padding: 5,
},
sliderHandle: {
width: 25,
aspectRatio: 1,
backgroundColor: '#f8f9ff',
borderRadius: 20,
position: 'absolute',
left: 5,
},
box: {
height: 30,
backgroundColor: '#b58df1',
borderRadius: 10,
},
boxWidthText: {
textAlign: 'center',
fontSize: 24,
},
rangePickerContainer: {
flexDirection: 'row',
alignItems: 'center',
},
});
export default RangePicker;
The issue I'm facing is the number are divided by 1000 and I don't know why