skip to Main Content

Here’s a simple demo (jsfiddle link):

const input = document.querySelector('#input');
const textarea = document.querySelector('#textarea');
const div = document.querySelector('div');
const x = (e) => {
  if (e.key === 'b') {
    const sel = document.getSelection();
    sel.modify('move', 'left', 'character');
  }
};
input.addEventListener('keyup', x);
textarea.addEventListener('keyup', x);
div.addEventListener('keyup', x);
<input type="text" id="input">
<textarea name="textarea" id="textarea" cols="30" rows="10"></textarea>
<div contenteditable></div>

On Chrome latest (116), pressing b into input (or textarea or div) puts the letter b into the box, and then moves the cursor left (so the contents are now <caret>b)

On Firefox latest (116), pressing b into input/textarea/div puts the letter b into the box, BUT it moves the cursor left only for the div.

On the documentation for Selection.modify, Firefox is listed as a fully supported browser. But, as you can see above, .modify('move', 'left', 'character') is not working on Firefox for <input>/<textarea> elements.

Is this is a bug? If so, are there any alternatives?

2

Answers


  1. I don’t know if this behavior is a bug or not. But, since it depends on control type, I changed your code slightly (also add some style and label to increase readability for myself) and tested it in Edge, Chrome, FireFox and Opera (all desktop versions) and it seemed to work.
    I’m not sure if it also works in mobile browsers because I cannot test it.

    function processKey(e) {
      if (e.key === 'a') {
        document.execCommand('selectAll', false);
      } else if (e.key === 'b') {
        var elm = document.activeElement;
        switch (elm.tagName.toLowerCase()) {
          case 'input':
          case 'textarea':
            var pos = elm.selectionStart;
            elm.selectionStart = pos - 1;
            elm.selectionEnd = pos - 1;
            break;
          case 'div':
            document.getSelection().modify('move', 'left', 'character');
        }
      }
    }
    
    [input, textarea, div].forEach(element =>
      element.addEventListener(`keyup`, processKey)
    );
    .part{
      display:flex;
      align-items:center;
      margin-top:20px;
    }
    
    .c1 {
      border: 1px solid red;
      width:200px;
    }
      <div class="part">
        <label for="input">Input:</label>
        <input type="text" id="input" class="c1"/>
      </div>
      <div class="part">
        <label for="textarea">TextArea:</label>
        <textarea name="textarea" id="textarea" cols="30" rows="10" class="c1"></textarea>
      </div>
      <div class="part">
        <label for="div">Div:</label>
        <div id="div" contenteditable="true" class="c1"></div>
      </div>
    Login or Signup to reply.
  2. Don’t use the modify function for inputs or textareas, instead use the dedicated selectionStart and selectionEnd properties that <textarea> and ` have for working with the caret/selections:

    const processKey = ({ target, key }) => {
      if (key === 'b') {
        const caret = target.selectionStart;
        target.selectionStart = caret - 1;
        target.selectionEnd = caret - 1;
      }
    };
    myInput.addEventListener('keyup', processKey);
    myTextArea.addEventListener('keyup', processKey);
    <input Id="myInput" type="text">
    <textarea id="myTextArea" cols="30" rows="10"></textarea>

    This won’t work for the contenteditable div, but the other answer shows you how to work with that type of content.

    (And while that means you’ll have two different event handlers for dealing with the different types of nodes, that’s actually a good thing. One function that does everything is generally bad code =)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search