The requirements:
The issue:
What I've tried:
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/
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/