0

In the following structure:

<div contenteditable="true"> <span class="some-class">Hello</span> World <!-- no span here --> <span class="some-other-class">and good bye!</span> </div> 

Is it possible to remove selected span elements, but not the text node, without interrupting the cursor position in the contenteditable div?

So even if the cursor is inside the span, keep the same text content, but just remove the span elements around it

For example if the styling of some-class is red text color and I put the cursor somewhere inside the word "Hello", then I want the styling to be gone and remove the wrapping span

1 Answer 1

1

Here ya go!

<div contenteditable="true"> <span class="some-class" style="color: red">Hello</span> <span class="some-class" style="color: blue">world</span> <span class="some-other-class" style="color: orange">and good bye!</span> <span style="color: green">test</span> </div> <script> const div = document.querySelector('div'); const getSelection = () => { let offset = 0; const selection = window.getSelection(); const range = selection.getRangeAt(0); let start = range.startOffset; let end = range.endOffset; if (selection.baseNode.parentNode.hasChildNodes()) { for (const node of selection.baseNode.parentNode.childNodes) { const cnode = node; if (cnode.nodeType == document.TEXT_NODE && !(offset + cnode.length > start)) { offset = offset + cnode.length; } if (cnode.nodeType == document.ELEMENT_NODE && !(offset + cnode.textContent.length > start)) { offset = offset + cnode.textContent.length; } } } start = start + offset; end = end + offset; return { start, end }; }; const removeSpan = ({ target, currentTarget: { selectionStart } }) => { // Get our selection points for within our span element const { start, end } = getSelection(); // Only run this logic for span elements if (target.tagName === 'SPAN') { // Create new text node from our span's innerText const text = document.createTextNode(target.innerText + ' '); // Replace span with just the text target.replaceWith(text); // Create a range and set selection range back to what it was to not affect cursor const range = document.createRange(); range.setStart(text, start); range.setEnd(text, end); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } }; div.addEventListener('click', removeSpan); </script>

Sign up to request clarification or add additional context in comments.

2 Comments

That looks great. But I encountered a boundary case: Click somewhere on the word test, then click somewhere on and good bye! and then put the cursor at the end of the word world (after the letter d) - there is an Uncaught IndexSizeError. Any idea why?
@B.DLiroy I'm not experiencing the same error with your test case, but I've gotten it before. The reason is that we're for some reason sometimes setting the start point of the selection range within a textnode to be greater than the text node's length. From what I've seen this error doesn't affect the functionality. Perhaps you can fix it since this code is already 99% what you want :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.