Using ch unit would work if the typeface was monospace, otherwise character width varies. I would approach this problem by rendering an inline element holding the same text, measuring it and hiding it instantly every time the input field value changes.
To do this it's best to create a separate component for rendering sentences, let's call it Sentence:
const Test = () => {
return (
<div className="App">
{value.map(({ sentence }, i) => {
return (
<Sentence
initialValue={sentence}
key={i}
/>
);
})}
</div>
);
};
Test would pass the initial value, then Sentence will continue maintaining its own state:
const Sentence = ({ initialValue }) => {
const [value, setValue] = React.useState(initialValue)
return (
<SentenceInput
type="text"
value={value}
onChange={(event) => {
setValue(event.target.value)
}}
/>
)
}
Next, I'd add a span element that will serve as a measurer element, where the text should be styled the same way as in input elements, so the measurements turn out accurate. In your example in Chrome that would mean setting the font size to 13.3333px.
Now for the trickiest part, we need to combine useEffect and useLayoutEffect; useEffect will make the measurer visible, then useLayoutEffect will measure it and hide it
This is the result:
const Sentence = ({ initialValue }) => {
const [value, setValue] = React.useState(initialValue)
const [visible, setVisible] = React.useState(false)
const [width, setWidth] = React.useState('auto')
const measurer = React.useRef(null)
React.useEffect(() => {
setVisible(true)
}, [value])
React.useLayoutEffect(() => {
if (visible) {
const rect = measurer.current.getBoundingClientRect()
setWidth(rect.width)
setVisible(false)
}
}, [visible])
return (
<>
<span
ref={measurer}
style={{ fontSize: '13.3333px' }}
>
{visible && value}
</span>
<SentenceInput
type="text"
value={value}
style={{ width: width + 1 }}
onChange={(event) => {
setValue(event.target.value)
}}
/>
</>
)
}
I added 1px to the computed width because it seems to remove a small horizontal scroll in the input fields.
This is for you to tweak further the way you want, for example how it should behave when it reaches the viewport width.