Search code examples
javascriptfocusmouseeventcontenteditable

Weird double click behavior with text highlighting, when dynamically changing the contentedible attribute of a div


The requirements:

  1. We need an editable div that only becomes editable after a user clicks on it
  2. The div goes back to being read-only when focus is taken away from the div

The issue:

  • I can achieve these 2 requirements with no issues, however I get unideal text highlighting behaviour, namely when double clicking
  • We expect that a single click when the div is not editable makes the div become editable. A click after this should set a selection in the div. However, if these two clicks happen too quickly, the second click is treated like its a double click - highlighting an entire word wheras only a selection shouldve been made. I don't know how to correct this behaviour.

What I've tried:

  • Simply using css, with user-select=none doesn't work
  • Perhaps need to preventDefault the mousedown event, and manually call .focus() on the div, but this approach has its difficulties in putting the caret in the right position

Demo Code:

The HTML is just a simple contenteditable div, with contenteditable initially as false (read-only). My real code is done with slatejs, but the issue can be demonstrated here

<div contenteditable="false">
  initial text
</div>

Some styling to make it nicer to differentiate between the read-only state and editable state

[contenteditable] {
  border: 6px solid #333;
  border-radius: 2px;
  padding: 2px;
  outline: none;
  min-height: 1em;
}

[contenteditable="false"] {
  border-color: rgb(238, 192, 192);
  color: #aaa;
  user-select: none;
}

[contenteditable="true"] {
  border-color: green;
  color: black;
}

The basic js to achieve the 2 requirements as stated:

const editor = document.querySelector('[contenteditable]');

let hasFocus = false;
function setHasFocus(newFocus) {
    editor.setAttribute('contenteditable', newFocus);
  hasFocus = newFocus;
}

editor.addEventListener('mousedown', e => {
    e.stopPropagation();
    if (!hasFocus) {
    e.preventDefault();
    setHasFocus(true);
  }
})

document.addEventListener('mousedown', () => setHasFocus(false));

In sandbox: https://jsfiddle.net/bv93sLn6/30/


Solution

  • The issue essentially occurs in the browser inaccurately assuming the click count is higher than it should be. I solved it by keeping a store of the previous range, and replacing the current range with the previous if need be.

    Sandbox: https://jsfiddle.net/2gvxrf3y/56/