Is there a CSS selector for when the caret is in the element?
Asked Answered
I

3

8

I have a contenteditable element, I want elements inside to apply a style when the caret is inside them.

In this example the style changes on :hover:

div{
caret-color: red;
}

span:hover {
  font-weight:bold;
}
<div contenteditable="true">
  <span>Sub element one</span> 
  text node
  <span>sub element two</span>
</div>

Here you can see the caret as I have styled it in red, but I've hovered over the other span:

example of the problem

Is there any way to apply a style like this, but when the caret is inside the element? So that the text in the span around the red line is bold?

The solution would look like this:

style following caret

A CSS solution would be ideal, but I would consider JS solutions if that's not possible.

Intermezzo answered 19/4, 2018 at 14:7 Comment(7)
Which caret? I don't get it.Jamesjamesian
@Jamesjamesian when you are editing and the cursor | is within the spanMeadowlark
@Huangism but you will lose the navigation between all the textMeadowlark
@TemaniAfif you don't, it will navigate like it is text, I tried it on FF but it seems the focus does not trigger when the cursor is in the input so it's no good. I think this has to use jsReconcilable
So far there is no CSS selector that could do this alone. There's a similar question solved with JS: #31582705Terti
This is as close as I could get: codepen.io/richardmauritz/pen/BxyYXo?editors=1010 You have to find out which element is at the carets position. But the problem is that the contenteditable can be changed, what will break the code.Jamesjamesian
@Jamesjamesian That looks good, but it only follows clicks - typing or cursor moves can also change the position of the caret, and (as you said) all the click events are lost when edited. Also probably better as an answer than as a comment.Intermezzo
J
4

With CSS?

No, there is not css selector for that.

With JavaScript?

Yes, you can do this with JavaScript using the .getSelection() function.

With the .getSelection() you can get the current position of the caret and the element the caret is on.

window.getSelection().focusNode.parentElement // Returns DOM node

window.getSelection()

Returns a Selection object representing the range of text selected by the user or the current position of the caret.

https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection

So using this information you can create a function which styles the element the caret is on

var ctl = document.getElementById('contenteditable');
var selection;

function styleNode() {
  // Get selection / caret position
  selection = window.getSelection();
  
  // Check if type is Caret and not Range (selection)
  if(selection.type === 'Caret') {
    // Remove styles       
    [...selection.focusNode.parentElement.parentElement.getElementsByTagName( "span" )].forEach( child => child.style.fontWeight = "normal" );
    
    // Only style <span> elements
    if(selection.focusNode.parentElement.tagName === 'SPAN') {
      // Set style on element where caret is
      selection.focusNode.parentElement.style.fontWeight = "bold";
    }
  }
}

// Removes styles on focusout
const reset = () => [...ctl.getElementsByTagName( "span" )].forEach( child => child.style.fontWeight = "normal" );

ctl.addEventListener("keyup", styleNode);
ctl.addEventListener("click", styleNode);
ctl.addEventListener("focusout", reset);
<div contenteditable id="contenteditable">
  <span>This is</span> some editable <span>content</span>
</div>
Jamesjamesian answered 19/4, 2018 at 16:10 Comment(4)
Cheers, something similar to this worked for me. One change I needed was keyup rather than keydown to ensure that the selection check happened after the caret change (try navigating your snippet with arrow keys and changes happen on the move after the relevant one because on keydown the caret is still in the original location)Intermezzo
Glad I could help / point to the right direction. Have a nice day!Jamesjamesian
The first half of this answer is talking about the mouse cursor while the second half is suggesting a solution based on the text caret. I think there might have been some confusion here. Also the only thing :focus has in relation with the mouse is that you can click certain elements to focus them.Aplite
@Aplite I agree with your feedback and provided a simpler anwser which is straight to the point. Thanks mate!Jamesjamesian
R
1

I think a different approach is needed. Since editing requires clicking the element, I would just set up a click event handler on the sub-elements to style them.

// Get reference to the contenteditable element
var container = document.querySelector("[contenteditable='true']");

// Set up a click event handler for the container
container.addEventListener("click", function(evt){
  // Loop over child elements and remove the active class
  Array.prototype.slice.call(container.querySelectorAll("*")).forEach(function(el){
    el.classList.remove("active");
  });
  // Apply the active class to the clicked element
  evt.target.classList.add("active");
});
.active {
  font-weight:bold;
}
<div contenteditable="true">
  <span>Sub element one</span> 
  text node
  <span>sub element two</span>
</div>
Ragsdale answered 19/4, 2018 at 14:18 Comment(0)
R
0

The event listener selectionchange is quite useful to detect the change of the caret position (due to mouse or keyboard). It detects should also work on mobiles when swiping on the spacebar to move the caret.

sel = window.getSelection()

document.addEventListener("selectionchange", e => {
    (x=document.querySelector('.caretover')) && x.classList.remove('caretover')
    if (sel.focusNode.parentNode.tagName == 'SPAN')
       sel.focusNode.parentNode.classList.add('caretover')
})
.caretover {
  font-weight: bold;
}
<div contenteditable>
  <span>Sub element one</span> 
  text node
  <span>sub element two</span>
</div>
Rhabdomancy answered 16/11, 2021 at 19:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.