How to set the caret (cursor) position in a contenteditable element (div)?
Asked Answered
S

13

297

I have this simple HTML as an example:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

I want simple thing - when I click the button, I want to place caret(cursor) into specific place in the editable div. From searching over the web, I have this JS attached to button click, but it doesn't work (FF, Chrome):

const range = document.createRange();
const myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

Is it possible to set manually caret position like this?

Stocky answered 6/6, 2011 at 7:50 Comment(1)
For everyone who wants to set the cursor simply to the end, see https://mcmap.net/q/83309/-how-to-move-the-cursor-to-the-end-of-a-contenteditable-entityCheesewood
O
384

In most browsers, you need the Range and Selection objects. You specify each of the selection boundaries as a node and an offset within that node. For example, to set the caret to the fifth character of the second line of text, you'd do the following:

function setCaret() {
    var el = document.getElementById("editable")
    var range = document.createRange()
    var sel = window.getSelection()
    
    range.setStart(el.childNodes[2], 5)
    range.collapse(true)
    
    sel.removeAllRanges()
    sel.addRange(range)
}
<div id="editable" contenteditable="true">
  text text text<br>text text text<br>text text text<br>
</div>

<button id="button" onclick="setCaret()">focus</button>

IE < 9 works completely differently. If you need to support these browsers, you'll need different code.

jsFiddle example: http://jsfiddle.net/timdown/vXnCM/

Orose answered 6/6, 2011 at 8:33 Comment(17)
Your solution works perfectly. Thanks a lot. Is there a chance it could be made to work in "text context" - that means position #5 would be the fifth letter on a screen and not the fifth letter in a code ?Stocky
@Frodik: You could use the setSelectionRange() function from the answer I wrote here: #6240639. As I noted in the answer, there are various things it won't handle correctly/consistently but it may be good enough.Orose
how about set the caret inside a span tag like this : <<div id="editable" contenteditable="true"> test1<br>test2<br><span></span> </div>Christiniachristis
Will the given code barf on IE<9, or just do nothing?Empathize
@MalcolmOcean: Barf, because IE < 9 has no document.createRange (or window.getSelection, but it won't get that far).Orose
This solution works great, but does not in fire fox 38.0.1. Any workaround to that?Marta
@undroid: The jsfiddle works fine for me in Firefox 38.0.5 on Mac.Orose
@TimDown you're right, I had a problem in my browser. Perfect solution.Marta
For me, I needed to give the element focus el.focus() beforehand in order for this to work in Firefox 40 on Mac OS X.Effeminize
It wouldn't work if you have an inline text. You can remove all tags <br /> and try again.Northumbrian
In order to be able to test your answer quickly, could you edit your answer into a runnable code snippet? Thank you in advance.Grummet
Why does your fiddle work only when I set the value of childNodes to [0],[2], and [4]? Why don't 1 or 3 work?Oblige
@Spero: child nodes 1 and 3 are <br> elements and it doesn't make sense to try to place a selection boundary within one of those so I think the browser ignores it.Orose
I suggest checking out Rangy if you're looking for a more consistent solution across browsers: github.com/timdown/rangy I'm using it within a React project on a controlled contenteditable, and it's been working like a dream.Retard
For me, with just a comma separated string inside the div and no <br>s, i had to use 1 as the child instead of 5, and then it worked perfectlyAquitaine
Small note: this solution won't work for anything created with Web Components. This is because the selection API isn't aware of anything in the Shadow DOM: twitter.com/bocoup/status/1459120679220092928 It's a known issue, though. And folks are working on a solution: github.com/WICG/webcomponents/issues/79Aggravation
Tim, we need your expertise at this question here, which was incorrectly marked as a duplicate questionWhipping
B
101

Most answers you find on contenteditable cursor positioning are fairly simplistic in that they only cater for inputs with plain vanilla text. Once you using html elements within the container the text entered gets split into nodes and distributed liberally across a tree structure.

To set the cursor position I have this function which loops round all the child text nodes within the supplied node and sets a range from the start of the initial node to the chars.count character:

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

I then call the routine with this function:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

The range.collapse(false) sets the cursor to the end of the range. I've tested it with the latest versions of Chrome, IE, Mozilla and Opera and they all work fine.

PS. If anyone is interested I get the current cursor position using this code:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

The code does the opposite of the set function - it gets the current window.getSelection().focusNode and focusOffset and counts backwards all text characters encountered until it hits a parent node with id of containerId. The isChildOf function just checks before running that the suplied node is actually a child of the supplied parentId.

The code should work straight without change, but I have just taken it from a jQuery plugin I've developed so have hacked out a couple of this's - let me know if anything doesn't work!

Barr answered 8/12, 2016 at 8:13 Comment(14)
Could you provide a jsfiddle of this working please? I'm struggling to figure out how this works as I'm not sure what node.id and parentId relate to without an example. Thanks :)Abbotson
@Abbotson - try this jsfiddle.net/nrx9yvw9/5 - for some reason the content editable div in this example is adding in some characters and a carriage return at the start of the text (it might even be jsfiddle itself doing it as it doesn;t do the same on my asp.net server).Barr
@Abbotson - the html elements within the contenteditable div get broken down into a tree structure with one node for each html element. The getCurrentCursorPosition gets the current selection position and goes back up the tree counting how many plain text characters there are. Node.id is the html element id, while parentId refers to the html element id it should stop counting back toBarr
This was really great, I wrapped the methods into a jQuery plugin for my usage, but then re-read your comment and you said you had one already. Do you have it shared in a repo somewhere that I can reference and provide credit?Lunation
It's on my todo list to write one that is completely separate from my UI code - I'll post it when I have a sec.Barr
I was struggling with html nodes + contenteditable + caret position, this was exactly what I needed, and worked for me in React without much modification, thank yo uso much.Elbe
for me it was not working in firefox. to make it work i just added to the setCurrentCursorPosition-method before the setting of the ranges a focus on the div like so: document.getElementById("test").focus().Hadwin
you are actually amazing. I was creating a rich text editor and this is just what i needed. works like a charm! wish i could double upvote!Carothers
In order to be able to test your different solutions quickly, could you edit your answer into runnable code snippets? Thank you in advance.Grummet
I've experienced an error that I couldn't manage to get rid of : <div contenteditable>someText <span>specialText</span> someMoreText</div> If the user deletes the first char after a closing node (here the &nbsp; after </span>), the caret jumps to a wrong position before the first node in the current div. (I used a trick to get rid of all that - and didn't use this code) Note that the html was modified when the user typed (coloring keywords, identifiers, numbers and quotes)Retirement
Yes, in fact even "plain" text is going to needing a more sophisticated solution like this if it has new lines. These will either be represented by BRs, DIVs or occasionally Ps, which will be interspersed between stretches of text.Haviland
Anyone managed to fix the issue with cursor when you hit enter?Educated
I also have issues with newlines when pressing enterFirefly
I made a codepen demo of this codepen.io/jeffward/pen/OJjPKYo with minor changes - I don't like using id, so you pass the functions a container (aka the root contenteditable div) instead.Waldgrave
C
37

I refactored @Liam's answer. I put it in a class with static methods, I made its functions receive an element instead of an #id, and some other small tweaks.

This code is particularly good for fixing the cursor in a rich text box that you might be making with <div contenteditable="true">. I was stuck on this for several days before arriving at the below code.

edit: His answer and this answer have a bug involving hitting enter. Since enter doesn't count as a character, the cursor position gets messed up after hitting enter. If I am able to fix the code, I will update my answer.

edit2: Save yourself a lot of headaches and make sure your <div contenteditable=true> is display: inline-block. This fixes some bugs related to Chrome putting <div> instead of <br> when you press enter.

How To Use

let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// insert code here that does stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();

Code

// Credit to Liam (Stack Overflow)
// https://mcmap.net/q/99894/-how-to-set-the-caret-cursor-position-in-a-contenteditable-element-div
class Cursor {
    static getCurrentCursorPosition(parentElement) {
        var selection = window.getSelection(),
            charCount = -1,
            node;
        
        if (selection.focusNode) {
            if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                node = selection.focusNode; 
                charCount = selection.focusOffset;
                
                while (node) {
                    if (node === parentElement) {
                        break;
                    }

                    if (node.previousSibling) {
                        node = node.previousSibling;
                        charCount += node.textContent.length;
                    } else {
                        node = node.parentNode;
                        if (node === null) {
                            break;
                        }
                    }
                }
            }
        }
        
        return charCount;
    }
    
    static setCurrentCursorPosition(chars, element) {
        if (chars >= 0) {
            var selection = window.getSelection();
            
            let range = Cursor._createRange(element, { count: chars });

            if (range) {
                range.collapse(false);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    
    static _createRange(node, chars, range) {
        if (!range) {
            range = document.createRange()
            range.selectNode(node);
            range.setStart(node, 0);
        }

        if (chars.count === 0) {
            range.setEnd(node, chars.count);
        } else if (node && chars.count >0) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent.length < chars.count) {
                    chars.count -= node.textContent.length;
                } else {
                    range.setEnd(node, chars.count);
                    chars.count = 0;
                }
            } else {
                for (var lp = 0; lp < node.childNodes.length; lp++) {
                    range = Cursor._createRange(node.childNodes[lp], chars, range);

                    if (chars.count === 0) {
                    break;
                    }
                }
            }
        } 

        return range;
    }
    
    static _isChildOf(node, parentElement) {
        while (node !== null) {
            if (node === parentElement) {
                return true;
            }
            node = node.parentNode;
        }

        return false;
    }
}
Cowage answered 2/7, 2020 at 16:26 Comment(5)
Is there another way to handle hitting Enter in the code above? In my case, it is something really not handy.Educated
@Educated I actually made a separate question with this code and offered a bounty to fix the enter bug. Nobody was able to fix it.Cowage
Life saver! I adapted it to typescipt and integrated it with braft-editor and react. Awesome, thxSoelch
@Cowage Do you have the link to the question? Depending on what you mean, I might have figured out a solutionRodomontade
@Rodomontade - #62705949Cowage
L
13

I made this for my simple text editor.

Differences from other methods:

  • High performance
  • Works with all spaces

usage

// get current selection
const [start, end] = getSelectionOffset(container)

// change container html
container.innerHTML = newHtml

// restore selection
setSelectionOffset(container, start, end)

// use this instead innerText for get text with keep all spaces
const innerText = getInnerText(container)
const textBeforeCaret = innerText.substring(0, start)
const textAfterCaret = innerText.substring(start)

selection.ts

/** return true if node found */
function searchNode(
    container: Node,
    startNode: Node,
    predicate: (node: Node) => boolean,
    excludeSibling?: boolean,
): boolean {
    if (predicate(startNode as Text)) {
        return true
    }

    for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
        if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
            return true
        }
    }

    if (!excludeSibling) {
        let parentNode = startNode
        while (parentNode && parentNode !== container) {
            let nextSibling = parentNode.nextSibling
            while (nextSibling) {
                if (searchNode(container, nextSibling, predicate, true)) {
                    return true
                }
                nextSibling = nextSibling.nextSibling
            }
            parentNode = parentNode.parentNode
        }
    }

    return false
}

function createRange(container: Node, start: number, end: number): Range {
    let startNode
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            const dataLength = (node as Text).data.length
            if (start <= dataLength) {
                startNode = node
                return true
            }
            start -= dataLength
            end -= dataLength
            return false
        }
    })

    let endNode
    if (startNode) {
        searchNode(container, startNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                const dataLength = (node as Text).data.length
                if (end <= dataLength) {
                    endNode = node
                    return true
                }
                end -= dataLength
                return false
            }
        })
    }

    const range = document.createRange()
    if (startNode) {
        if (start < startNode.data.length) {
            range.setStart(startNode, start)
        } else {
            range.setStartAfter(startNode)
        }
    } else {
        if (start === 0) {
            range.setStart(container, 0)
        } else {
            range.setStartAfter(container)
        }
    }

    if (endNode) {
        if (end < endNode.data.length) {
            range.setEnd(endNode, end)
        } else {
            range.setEndAfter(endNode)
        }
    } else {
        if (end === 0) {
            range.setEnd(container, 0)
        } else {
            range.setEndAfter(container)
        }
    }

    return range
}

export function setSelectionOffset(node: Node, start: number, end: number) {
    const range = createRange(node, start, end)
    const selection = window.getSelection()
    selection.removeAllRanges()
    selection.addRange(range)
}

function hasChild(container: Node, node: Node): boolean {
    while (node) {
        if (node === container) {
            return true
        }
        node = node.parentNode
    }

    return false
}

function getAbsoluteOffset(container: Node, offset: number) {
    if (container.nodeType === Node.TEXT_NODE) {
        return offset
    }

    let absoluteOffset = 0
    for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) {
        const childNode = container.childNodes[i]
        searchNode(childNode, childNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                absoluteOffset += (node as Text).data.length
            }
            return false
        })
    }

    return absoluteOffset
}

export function getSelectionOffset(container: Node): [number, number] {
    let start = 0
    let end = 0

    const selection = window.getSelection()
    for (let i = 0, len = selection.rangeCount; i < len; i++) {
        const range = selection.getRangeAt(i)
        if (range.intersectsNode(container)) {
            const startNode = range.startContainer
            searchNode(container, container, node => {
                if (startNode === node) {
                    start += getAbsoluteOffset(node, range.startOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                start += dataLength
                end += dataLength

                return false
            })

            const endNode = range.endContainer
            searchNode(container, startNode, node => {
                if (endNode === node) {
                    end += getAbsoluteOffset(node, range.endOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                end += dataLength

                return false
            })

            break
        }
    }

    return [start, end]
}

export function getInnerText(container: Node) {
    const buffer = []
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            buffer.push((node as Text).data)
        }
        return false
    })
    return buffer.join('')
}
Loop answered 5/1, 2020 at 11:23 Comment(0)
C
9
  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }
Croquet answered 27/3, 2019 at 12:52 Comment(0)
H
8

I'm writting a syntax highlighter (and basic code editor), and I needed to know how to auto-type a single quote char and move the caret back (like a lot of code editors nowadays).

Heres a snippet of my solution, thanks to much help from this thread, the MDN docs, and a lot of moz console watching..

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

This is in a contenteditable div element

I leave this here as a thanks, realizing there is already an accepted answer.

Horsemint answered 9/10, 2018 at 7:52 Comment(0)
I
7

If you don't want to use jQuery you can try this approach:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDiv you editable element, don't forget to set an id for it. Then you need to get your innerHTML from the element and cut all brake lines. And just set collapse with next arguments.

Ink answered 9/3, 2018 at 9:9 Comment(0)
H
4

function set_mouse() {
  var as = document.getElementById("editable");
  el = as.childNodes[1].childNodes[0]; //goal is to get ('we') id to write (object Text) because it work only in object text
  var range = document.createRange();
  var sel = window.getSelection();
  range.setStart(el, 1);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);

  document.getElementById("we").innerHTML = el; // see out put of we id
}
<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd
  <p>dd</p>psss
  <p>dd</p>
  <p>dd</p>
  <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>

It is very hard set caret in proper position when you have advance element like (p) (span) etc. The goal is to get (object text):

<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
    <p>dd</p>
    <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>

    function set_mouse() {
        var as = document.getElementById("editable");
        el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
        var range = document.createRange();
        var sel = window.getSelection();
        range.setStart(el, 1);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        document.getElementById("we").innerHTML = el;// see out put of we id
    }
</script>
Helio answered 26/8, 2016 at 8:20 Comment(1)
In order to be able to test your answer quickly, could you edit your answer into a runnable code snippet? Thank you in advance.Grummet
R
4
var sel = window.getSelection();
sel?.setPosition(wordDiv.childNodes[0], 5);
event.preventDefault();
Rochelle answered 31/3, 2021 at 1:4 Comment(0)
B
3

I've readed and tried some cases from here and just put here what is working for me, considering some details according dom nodes:

  focus(textInput){
    const length = textInput.innerText.length;
    textInput.focus();

    if(!!textInput.lastChild){
      const sel = window.getSelection();
      sel.collapse(textInput.lastChild, length);
    }
  }
Binaural answered 3/1, 2022 at 12:19 Comment(1)
Thanks for the trick, turns out both calling focus() and using lastChild (even if the editable div is just plain text) is necessary to make it work.Hattiehatton
B
1

I think it's not simple to set caret to some position in contenteditable element. I wrote my own code for this. It bypasses the node tree calcing how many characters left and sets caret in needed element. I didn't test this code much.

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

I also wrote code to get current caret position (didn't test):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

You also need to be aware of range.startOffset and range.endOffset contain character offset for text nodes (nodeType === 3) and child node offset for element nodes (nodeType === 1). range.startContainer and range.endContainer may refer to any element node of any level in the tree (of course they also can refer to text nodes).

Barometer answered 2/5, 2019 at 9:35 Comment(0)
E
1

Based on Tim Down's answer, but it checks for the last known "good" text row. It places the cursor at the very end.

Furthermore, I could also recursively/iteratively check the last child of each consecutive last child to find the absolute last "good" text node in the DOM.

function onClickHandler() {
  setCaret(document.getElementById("editable"));
}

function setCaret(el) {
  let range = document.createRange(),
      sel = window.getSelection(),
      lastKnownIndex = -1;
  for (let i = 0; i < el.childNodes.length; i++) {
    if (isTextNodeAndContentNoEmpty(el.childNodes[i])) {
      lastKnownIndex = i;
    }
  }
  if (lastKnownIndex === -1) {
    throw new Error('Could not find valid text content');
  }
  let row = el.childNodes[lastKnownIndex],
      col = row.textContent.length;
  range.setStart(row, col);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);
  el.focus();
}

function isTextNodeAndContentNoEmpty(node) {
  return node.nodeType == Node.TEXT_NODE && node.textContent.trim().length > 0
}
<div id="editable" contenteditable="true">
  text text text<br>text text text<br>text text text<br>
</div>
<button id="button" onclick="onClickHandler()">focus</button>
Eaglestone answered 2/12, 2019 at 20:23 Comment(0)
M
0
move(element:any,x:number){//parent
    let arr:Array<any>=[];
    arr=this.getAllnodeOfanItem(this.input.nativeElement,arr);
    let j=0;
    while (x>arr[j].length && j<arr.length){
        x-=arr[j].length;
        j++;
    }
    

    
    var el = arr[j];
    var range = document.createRange();
    var sel = window.getSelection();
    range.setStart(el,x );
    range.collapse(true);
    if (sel)sel.removeAllRanges();
    if (sel)sel.addRange(range);
}   

getAllnodeOfanItem(element:any,rep:Array<any>){
    let ch:Array<any>=element.childNodes;
    if (ch.length==0 && element.innerText!="")
        rep.push(element);
    else{
        for (let i=0;i<ch.length;i++){
            rep=this.getAllnodeOfanItem(ch[i],rep)
        }
    }
    return rep;
}
Murphy answered 16/10, 2021 at 10:47 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Foeman

© 2022 - 2024 — McMap. All rights reserved.