How do I prevent Element.focus() change cursor position in contenteditable div?

Viewed 655

I do not wish to move the cursor, anywhere but when I call the function focus, it moves the cursor to the beginning. I wish to keep the cursor where it is. I am doing this in ReactJS using Typescript.

const element = document.getElementById('editable-div');
element.focus();   // sets cursor to beginning here
// rest of my code is here

I want to call focus because I need to append stuff in the contenteditable div and in some cases insert stuff. But as soon as I do focus(), it moves the cursor at the wrong place. How do I fix this?

1 Answers

With the help of document.createRange and Window.getSelection it is possible

const { useState, useEffect, useRef } = React;

const random = (from, to) => Math.floor(Math.random() * (to - from) + from);
const randomText = () => Array(random(5, 10)).fill(0).map((pr, index) => String.fromCharCode(index + 50)).join('')

const setCaretToTheEnd = (element) => {
  const position = element.textContent.length;
  const [childNode] = element.childNodes;
  const range = document.createRange();
  const selection = window.getSelection();
  range.setStart(childNode, position);
  range.setEnd(childNode, position);
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);
}

const App = () => {
  const [text, setText] = useState('');
  const inputRef = useRef(null);
  
  useEffect(() => {
    if(inputRef && inputRef.current && text) {
      const element = inputRef.current;
            
      setCaretToTheEnd(element);
      element.focus();
    }
  }, [text]);
  
  useEffect(() => {
    let isUnmounted = false;
    let handle = null;
    
    const changeText = () => {
      setText(randomText());
    
      handle = setTimeout(changeText, 4000);
    }
    
    handle = setTimeout(changeText, 500);
    
    return () => {
      isUnmounted = true;
      clearTimeout(handle);
    }
  }, [])

  const onChange = ({target: {value}}) => setText(value);

  return <div>
    <div ref={inputRef} suppressContentEditableWarning={true} className="contentEditable" onChange={onChange} contentEditable>{text}</div>
  </div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
.contentEditable {
  padding: 1rem;
  border: 1px solid black;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
<div id="root"></div>
<script src="https://unpkg.com/material-ui-lab-umd@4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>

Related