JSFiddle - React, Tailwind, and code Playground

HTML

<p class="sentence">
  <span class="word" index="0">There </span><span class="word" index="1">is </span><span class="word" index="2">a </span><span class="word" index="3">big, </span><span class="word" index="4">wonderful </span><span class="word" index="5">world.</span>
</p>

<p style="color: grey; margin-top: 10em">
  Don't forget to expand the console (below) to see output.
</p>

CSS

.word {
  border: 1px solid green;
  font-size: 2.5em;
  
  /* margin: 3px; */
}

JavaScript

$('.word').click(function() {
  let word_index = parseInt($(this).attr('index'));
  
	let selection  = window.getSelection();
  let char_index = selection.focusOffset;
  
  // If we hit an overlap with another bit of selectable
  // text, we zero out the cursor offset to avoid using
  // that word's offset -- the left edge of a textnode
  // is the only place where this happens, so we know
  // the correct cursor offset is 0.
  if ($(this).text() !== selection.focusNode.wholeText) 	{
    char_index = 0;
  }

	// There's also a small clickable area on the right
  // side of the final character in a word that would
  // result in a cursor at the end of that word (often
  // where the delimiting space is). We simply shift
  // the cursor's reference from end-of-that-word to
  // start-of-the-next word, which are the same to the
  // user, but makes more sense for keeping proper
  // word individuation.
  if (char_index === $(this).text().length) {
    word_index += 1;
    char_index  = 0;
  }

	console.log('Clicked to set a new cursor @');
  console.log('Word index = ' + word_index.toString() + 
  				 ' / char index = ' + char_index.toString() );
});