Get the range of start and end offset values without considering its immediate parent

Viewed 204

I have two span elements inside of p tag. While selecting some text in that p I need to get the start and end offset of selected value using window.getSelection().

My expectation is that while selecting the second span text like 'country', the start offset value would be 28 and end offset would be 34 without considering its immediate parent. However the actual value of start offset is 10 and end offset is 16.

How can I meet my expected values?

<p style="margin: 0px;">
  <span>Welcome to India which</span>
  <span>
    <span>was great country for tourist places</span>
  </span>
</p>
private onKeyUp(event: any): void {
  let labelSelection = window.getSelection ? window.getSelection() : document.getSelection();
  let range: any = this.labelSelection.getRangeAt(0);
}
1 Answers

It's quite a tricky problem, but was enjoyable to play around with.

Here's one day to do it:

Attach mouse event listeners to the containing element. Use event.target to identify the start and end nodes that were selected. Use these nodes to add offsets to the numbers reported by the raw selection.

Html:

<p id="outer" style="margin: 0px;">
  <span>Welcome to India which</span>
  <span>
    <span>was great country for tourist places</span>
  </span>
</p>

Javascript:

const flattenNodes = node =>
  Array.from(node.children).reduce((acc, curr) => {
    if (curr.childElementCount > 0) {
      acc.push(flattenNodes(curr))
    } else {
      acc.push(curr)
    }
    return acc
  }, [])

const getOffsetForElementAtIndex = nodes => elementIndex =>
  nodes.reduce((sum, node, i) => (i < elementIndex ? sum + node.textContent.length : sum), 0)

const getSelection = ev => {
  const nodes = flattenNodes(ev.currentTarget).flat()
  const startElementIndex = nodes.indexOf(mouseDownElement)
  const endElementIndex = nodes.indexOf(ev.target)
  const { startOffset, endOffset } = document.getSelection().getRangeAt(0)

  const start = startOffset + getOffsetForElementAtIndex(nodes)(startElementIndex)
  const end = endOffset + getOffsetForElementAtIndex(nodes)(endElementIndex)
  console.log(`start: ${start}, end: ${end}`)
}
let mouseDownElement = null

const el = document.getElementById('outer')
el.addEventListener('mousedown', ev => (mouseDownElement = ev.target))
el.addEventListener('mouseup', ev => getSelection(ev))

codepen here

Related