Search code examples
javascriptinputevent

InputEvent.getTargetRanges() always empty?


I have a Div in my page that is set up as contenteditable=true, e.g.

<DIV contenteditable=true id="editor">Test</DIV>

I've got a simple function that dumps out event info whenever a change is made in the div:

document.getElementById("editor").addEventListener("input", function(e) {
    console.log("input event fired:",e)
    console.log("data:",e.data)
    console.log("dataTransfer:",e.dataTransfer)
    console.log("inputType:",e.inputType)
    console.log("isComposing:",e.isComposing)
    console.log("target ranges => ", e.getTargetRanges())
}, false);

The goal is to use the input event to construct a log of changes made to the div that can be replayed later. For performance reasons, it is not desirable to try to do some diff comparing the div to its previous value. Instead, the expectation is that the input event should provide the information about exactly what change in a compact manner.

Per the documentation,

The getTargetRanges() property of the InputEvent interface returns an array of static ranges that will be affected by a change to the DOM if the input event is not canceled.

This function is supposed to return an array of StaticRange objects.

But whenever I see the input event fired, this function always returns an empty array. As a result, insufficient information is available to determine what the change what. For instance, if I type "x" I see e.data equals "x", and inputType equals "insertText" so I know that "x" has been inserted somewhere, but e.getTargetRanges() returns an empty array, so I have no idea where it was inserted.

(This is on Chrome 77, and according to the chart this feature has been implemented in Chrome since version 60).


Solution

  • You get the ranges when using the beforeinput event. However, be aware of its limited compatibility.

    document.getElementById("editor").addEventListener("beforeinput", function(e) {
        if (e.data)
          console.log("data:",e.data);
        if (e.dataTransfer)
        	console.log("dataTransfer data:", e.dataTransfer.getData('text'));
        const targetRanges = e.getTargetRanges();
        if (targetRanges && targetRanges.length > 0)
          console.log("target ranges => ", { startOffset: targetRanges[0].startOffset, endOffset: targetRanges[0].endOffset });
    }, false);
    <DIV contenteditable=true id="editor">Test</DIV>