JSFiddle - React, Tailwind, and code Playground

by Graham Dixon

HTML

<div class="editor">
<span class="line"><span class="line-content">These <span class="line-content">test </span></span><h1 style="font-size:20px;"><u><span class="line-content">lines</span></u></h1><span class="line-content"> can be fake selected</span></span>
  <span class="line"><span class="line-content">These lines can be fake selected</span></span>
    <span class="line"><span class="line-content">These lines can be fake selected</span></span>
      <span class="line"><span class="line-content">These lines can be fake selected</span></span>
        <span class="line"><table>
        <tr><td><span class="line-content">These lines can be fake selected</span></td><td><span class="line-content">These lines can be fake selected</span></td></tr>
        </table></span>
</div>

CSS

.selected-content:not(.line), .line.selected-content .line-content {
  background: #ACCEF7
}

.line {
  user-select: none;
  display: inline-block;
  cursor: text;
  width: 100%;
}

.fake-selection {
  position: absolute;
  color: transparent;
  z-index: -1;
}

.line-content {
  z-index: 1;
}

JavaScript

// climbing to parent with matching selector
const closest = function(elem, selector) {

    // Element.matches() polyfill
    if (!Element.prototype.matches) {
        Element.prototype.matches =
            Element.prototype.matchesSelector ||
            Element.prototype.mozMatchesSelector ||
            Element.prototype.msMatchesSelector ||
            Element.prototype.oMatchesSelector ||
            Element.prototype.webkitMatchesSelector ||
            function(s) {
                var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                    i = matches.length;
                while (--i >= 0 && matches.item(i) !== this) {}
                return i > -1;
            };
    }

    // Get the closest matching element
    for (; elem && elem !== document; elem = elem.parentNode) {
        if (elem.matches(selector)) return elem;
    }

    // undiscovered
    return null;
};

// range to get points from the dom
const range = function(e) {
    // diiferent environments use different methods to get to the same values
    var range, textNode, offset;
    // switch to get the correct range from point method
    if (document.caretPositionFromPoint) {
        // standard uses caretPositionFromPoint
        range = document.caretPositionFromPoint(e.pageX, e.pageY);
        textNode = rage && range.offsetNode;
        offset = range && range.offset;
    } else if (document.caretRangeFromPoint) {
        // webkit uses caretRangeFromPoint
        range = document.caretRangeFromPoint(e.pageX, e.pageY);
        textNode = range && range.startContainer;
        offset = range && range.startOffset;
    }
    // ensure we have a range to return
    if (range) {

        // return the response object
        return {
            range: range,
            textNode: textNode,
            offset: offset,
            x: e.pageX,
            y: e.pageY
        }
    }
};

// set up selection - from outside create a new selection instance per...