JSFiddle - React, Tailwind, and code Playground

by mtrantalainen

HTML

<h1>Demo for incorrect <code>Range.getBoundingClientRect()</code> behavior:</h1>

<input></input>

<div id="testarea" contenteditable>
<p>Here's some text.</p>
<table>
<tr><td>a</td><td>b</td></tr>
<tr><td>c</td><td></td></tr>
</table>
<p>Set caret to this text and then repeatedly extend selection with key combination <kbd>Shift</kbd> + <kbd>Left Arrow</kbd> repeatedly until selection start is in the cell marked with letter "c". Note that that bug appears both with extended selection range and with collapsed selection range (that is, while moving the cursor with arrow keys only).</p>
<p>
Note that the bounding box is all zeroes when caret is actually on the right side of the table (caret is rendered as full table height), or when the caret is in the forth (empty) table cell, or caret is on the left side of the table. The bounding box should cover the area used for the blinking caret. Note the #visualfocus element skipping to top left corner instead!</p>

<p>There seems to be similar problem with <b>right edge</b> of images such as <img alt="[image here]"> (only alt text intentionally shown). However, the image also shows the same problem on the left edge if the element is first component on a single line!</p>
</div><!-- end of #testarea -->

<div id="visualfocus"></div>
<code id="textboundingbox"></code>

CSS

table {
  border-collapse: collapse;
  margin: auto;
}

td {
  border: solid 0.5rem rgba(0,128,0, 0.2);
  padding: 1rem;
}

kbd {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  background: #ccc;
  color: #000;
  border-radius: 0.25rem;
  border: outset;
}

#testarea
{
  padding: 1rem;
  margin: 1rem;
  border: solid #ccc 0.5rem;
}

#visualfocus
{
  border: solid #f007 1px;
  position: absolute;
  box-sizing: border-box;
  pointer-events: none;
  box-shadow: 0 0 0.1rem 0.5rem #00f7;
}

code
{
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

JavaScript

let visualfocus = document.getElementById("visualfocus");
let testarea = document.getElementById("testarea");
let textboundingbox = document.getElementById("textboundingbox");

testarea.addEventListener("keydown", function()
{
  // do following after flushing event queue once
  setTimeout(function()
  {
  let selection = window.getSelection();
  // create collapsed range at the caret (regardless of selection length):
  let range = new Range();
	range.setStart(selection.focusNode, selection.focusOffset);
	range.setEnd(selection.focusNode, selection.focusOffset);
  let boundingbox = range.getBoundingClientRect();
  // Note that this demo simply puts bounding box values to target element, this doesn't work if content is scrolled downwards
	visualfocus.style.top = boundingbox.top + "px";
	visualfocus.style.left = boundingbox.left + "px";
	visualfocus.style.width = boundingbox.width + "px";
	visualfocus.style.height = boundingbox.height + "px";
  textboundingbox.innerText = JSON.stringify(boundingbox, null, 2);
  }, 0);
});