I have two ways of thinking about this problem:
- The first answers your code question directly (how do I detect when the next flex element wraps).
- The second addresses your cursor problem with an alternative approach.
Detecting a Flex Wrap
You might be able to detect a wrap by using the actual positions of the objects via getBoundingClientRect. This function gives you where something is relative to the viewport. Crucially, we know an element "wraps" if its vertical location is different from that of the previous element.
const isWrapping = (previous, current) =>
previous.getBoundingClientRect().top !== current.getBoundingClientRect().top
const isWrapping = (previous, current) =>
previous.getBoundingClientRect().top !== current.getBoundingClientRect().top
const highlightWrappingElements = (container) => {
for (let i = 1; i < container.children.length; ++i) {
const previous = container.children[i - 1]
const current = container.children[i]
if (isWrapping(previous, current))
current.classList.add('wrapping')
}
}
highlightWrappingElements(document.querySelector('#container'))
.container {
width: 30rem;
border: 1px solid black;
gap: 0.5rem;
display: flex;
flex-wrap: wrap;
}
.box {
width: 5rem;
height: 5rem;
background: blue;
}
.wide { width: 7.5rem; }
.wrapping { background: orange; }
<div id="container" class="container">
<div class="box wide"></div>
<div class="box"></div>
<div class="box wide"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box wide"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box wide"></div>
<div class="box wide"></div>
<div class="box"></div>
<div class="box"></div>
</div>
In the example, elements that start a new line are orange.
How you would integrate this technique into your problem depends on how your React code is set up, but the idea should be a good starting point.
Moving a Caret
Your actual goal is to move a caret through a paragraph of letters. To do so accurately, it must:
- Know the length of each letter to position itself accurately
- Know where the end of the line is so it can wrap
Those are non-trivial problems, especially if the box has to resize or the paragraph content is dynamic. That is, if you are trying to position the caret manually with position: absolute.
Instead, you might be able to actually embed the caret in the text itself.
<p>This is s<span class="caret"></span>ome text.</p>
This solves the above two issues immediately, as the caret will always be exactly where it needs to be as long as you rerender the node when the caret's position changes.
Warning: On Chrome, the caret span breaks the word which results in it wrapping at the end of a line. This can be fixed by, for example, wrapping each word in a span set to display: inline-block. I have left out how to do that in the code below for brevity.
const paragraph = document.querySelector('#paragraph')
const content = paragraph.textContent
const caret = document.createElement('span')
caret.classList.add('caret')
let caretPosition = 0
const advanceCaret = () => {
caretPosition += 1
if (caretPosition >= content.length)
caretPosition = 0
paragraph.innerHTML = content.substring(0, caretPosition) + caret.outerHTML + content.substring(caretPosition)
}
setInterval(advanceCaret, 100)
.container {
width: 30rem;
border: 1px solid black;
padding: 1rem;
font-size: 1.25rem;
}
p { margin: 0; }
.caret {
position: relative;
overflow: visible;
}
.caret::before {
content: '';
border-left: 2px solid red;
position: absolute;
height: 100%;
top: 0;
left: 0;
}
<div class="container">
<p id="paragraph">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
I prefer this as it's simple and avoids most of the complexity with manual positioning, but whether it's viable is up to how you are ultimately rendering your paragraph of text.