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.