Firefox Sets Wrong Caret Position Contenteditable with :Before

Firefox showing caret in wrong position?

Seems that adding this css rule to "container" solves the problem in most cases:

white-space: pre-wrap;

contentEditable cursor position/style in FireFox

I waited and used the content editable only in Firefox 4 and higher. I filed this and a few other bugs which the Mozilla team has fixed for FF 4.

Why is the caret invisible in a contenteditable with position:relative?

Don't know the exact cause of the issue but you can fix it in multiple ways.

My previous explanation (maybe wrong) :

This isn't a bug, you put the background color on a <span> tag (in
position:relative) inside a contenteditable div element and so the
span is on top of the contenteditable div.

I still think is related to the z-index since we can see on the image below the red background on top of the Chrome focus border:

Sample Image

Remove position:relative

Removing position:relative of the <span> fix the issue:

.no-bug {
background-color: red;
}
<div contenteditable>
This has caret
<span class="no-bug">This has caret !</span>
this has caret
</div>

Firefox contenteditable cursor issue

Here's a fiddle that you might like: http://jsfiddle.net/e5gwc1gq/2/

I believe that the problem is because the :before psuedo element doesn't allow access to the underlying parent-div.

The reason I think so is because if you were to replace before with after, the problem persists, but is flipped, ie, now if you click in the left, it won't work properly.

Note: You might want to add word-wrap: break-word; css rule for .content-editable, since firefox doesn't add this by default, but chrome does.

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

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>

setting the caret/cursor position to the end of a contenteditable div

No idea if this will help anyone else out. This does not work cross-browser, but since my issue was specific to FireFox and how it handles range/selection with BR tags, this seemed to solve my issue.

I can basically just set the end of the range before the BR

range.setEndBefore($(el).find('br')[0]);

so in my function I am doing this only for firefox:

 function placeCaretAtEnd(el) {
el.focus();
if (window.getSelection){
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
if ($('#browser-version-check').val() == 'firefox') {
range.setEndBefore($(el).find('br')[0]);
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
}

And here is an updated jsfiddle http://jsfiddle.net/mstefanko/fG5kJ/2/, did not include browser check so this demo will only work correctly in FireFox

Set cursor position on contentEditable div

This is compatible with the standards-based browsers, but will probably fail in IE. I'm providing it as a starting point. IE doesn't support DOM Range.

var editable = document.getElementById('editable'),
selection, range;

// Populates selection and range variables
var captureSelection = function(e) {
// Don't capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;

while(parentAnchor && parentAnchor != document.documentElement) {
if(parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}

while(parentFocus && parentFocus != document.documentElement) {
if(parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}

if(!isOrContainsAnchor || !isOrContainsFocus) {
return;
}

selection = window.getSelection();

// Get range (standards)
if(selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);

// Get range (Safari 2)
} else if(
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};

// Recalculate selection while typing
editable.onkeyup = captureSelection;

// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
editable.className = editable.className + ' selecting';
};
document.onmouseup = function(e) {
if(editable.className.match(/\sselecting(\s|$)/)) {
editable.className = editable.className.replace(/ selecting(\s|$)/, '');
captureSelection();
}
};

editable.onblur = function(e) {
var cursorStart = document.createElement('span'),
collapsed = !!range.collapsed;

cursorStart.id = 'cursorStart';
cursorStart.appendChild(document.createTextNode('—'));

// Insert beginning cursor marker
range.insertNode(cursorStart);

// Insert end cursor marker if any text is selected
if(!collapsed) {
var cursorEnd = document.createElement('span');
cursorEnd.id = 'cursorEnd';
range.collapse();
range.insertNode(cursorEnd);
}
};

// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById('cursorStart'),
cursorEnd = document.getElementById('cursorEnd');

// Don't do anything if user is creating a new selection
if(editable.className.match(/\sselecting(\s|$)/)) {
if(cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if(cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if(cursorStart) {
captureSelection();
var range = document.createRange();

if(cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);

// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);

// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);

// Select range
selection.removeAllRanges();
selection.addRange(range);

// Delete cursor marker
document.execCommand('delete', false, null);
}
}

// Call callbacks here
for(var i = 0; i < afterFocus.length; i++) {
afterFocus[i]();
}
afterFocus = [];

// Register selection again
captureSelection();
}, 10);
};

Caret disappears in Firefox when saving its position with Rangy

Apparently rangy's Selection Save and Restore module can't be used to keep track of the current selection while the user interacts with a contenteditable, like you want.

I digged into it a bit, and the problem is that rangy inserts hidden <span>s as markers, and updates the selection to be after the marker, instead of keeping it inside the #text node the user's editing:

 <div contenteditable>
#text [This is something I typed <!-- selection is moved from here -->]
<span class="rangySelectionBoundary"/>
<!-- to here -->
</div>

Firefox has trouble displaying the caret in this scenario (I haven't found a bug about this specific issue, but here's a similar one where the caret is not displayed when the selection is between two <span>s).

Commenting this code out seems to fix the issue with the disappearing caret. It's unclear to me why that code is needed -- it was added before 1.0 in a large commit with its message saying: "Fixes for save/restore problems with control ranges and multiple range selections. Added demos for save/restore and CSS class applier modules." -- so I'm not comfortable to suggest fixing this in rangy (and since it's unmaintained for a few years, I don't have much hope in getting its author's input on this).

So I tried to figure out why you needed this in the first place, to suggest other solutions not involving rangy.saveSelection (for example, rangy's Text Range module provides getSelection().saveCharacterRanges(containerNode) that works without modifying the DOM.

It appears that you have a <div contenteditable> and some "buttons" (<span>s), clicking on which would insert some HTML at the caret position. The problem you were trying to solve was that when the "buttons" were clicked, the selection moved from the contenteditable into the button, and you were unable to detect the insert position.

Instead of storing and restoring the selection, you can instead make the buttons user-select: none - this will keep the caret in the contenteditable.

To test this, I commented out all references to rangy.saveSelection and rangy.restoreSelection and changed the this.$refs.divInput.focus(); call in the "button"'s onclick handler to run only when the contenteditable wasn't already focused by wrapping it in an if (!this.$refs.divInput.contains(document.activeElement)). See how this works in this updated fiddle:

https://jsfiddle.net/fjxsgvm2/



Related Topics



Leave a reply



Submit