Does clicking outside a contenteditable div still give it focus?

Due to a specific requirement, I must utilize a contenteditable div instead of a standard text input for entering text. Everything was going smoothly until I discovered that setting the contenteditable div with display: inline-block causes it to receive focus even when clicking outside the div!

I need the div to only receive focus when the user clicks directly on it, not in its vicinity. Currently, I observed that if the user clicks elsewhere and then clicks on the same row as the div, it still gets focus.

Here is a basic example illustrating the issue:

HTML:

<div class="outside">
    <div class="text-input" contenteditable="true">
        Input 1
    </div>
    <div class="text-input" contenteditable="true">
        Input 2
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't affect the contenteditable div
      but clicking on the side does.
    </div>
</div>

CSS:

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}

View JSFiddle demonstrating the issue

Is there a way (through CSS or javascript) to ensure the browser only gives focus to the div when clicked directly, rather than on the same row?

P.S. I've come across a similar issue (link to related post), but the suggested solution does not work in my case.

Answer №1

Explanation (if this doesn't interest you, feel free to skip to the Workarounds below)

Upon clicking on an editable element, the browser positions a cursor (referred to as an insertion point) in the closest text node within the clicked element, at the same vertical level as where you clicked. This text node might be directly within the clicked element or nested inside one of its child elements. You can experiment with this behavior by running the code snippet provided below and interacting within the large blue box.

The blue box represents a <div> with the contenteditable attribute, while the inner orange/yellow boxes denote nested child elements. Note that if you click close to but not inside one of the child elements, the cursor will still end up inside it, even if your click was outside. Though counterintuitive, this is not a glitch. Given that the element clicked (the blue box) is editable and the child element is part of its content, it makes sense for the cursor to be placed in the child element if that's where the nearest text node lies.

The issue arises when Webkit browsers (Chrome, Safari, Opera) exhibit similar behavior even when the contenteditable attribute is set on the child rather than the parent. In such cases, the browser erroneously searches for the nearest text node within the editable child, resulting in a blinking cursor. From my perspective, this behavior qualifies as a bug; Webkit browsers are executing the following logic:

on click:
  find nearest text node within clicked element;
  if text node is editable:
    add insertion point;

...when they should ideally adhere to this approach:

on click:
  if clicked element is editable:
    find nearest text node within clicked element;
    add insertion point;

Interestingly, block elements like divs do not seem to encounter this bug, leading me to agree with @GOTO 0's answer, which implicates text selection being governed by the same logic dictating insertion point placement. Clicking multiple times outside an inline element highlights the enclosed text, a behavior absent for block elements. It appears there is a correlation between the absence of an insertion point upon clicking outside a block and this different highlighting behavior. The first workaround listed below leverages this exception.


Workaround 1 (nested div)

To circumvent this issue, considering blocks are immune to the bug, embedding a div within the inline-block and assigning it the editable property seems like the most effective solution. Since inline-blocks internally behave akin to block elements, introducing a nested div shouldn’t impact their behavior.

.container {width: auto; padding: 20px; background: cornflowerblue;}
.container * {margin: 4px; padding: 4px;}
div {width: 50%; background: gold;}
span {background: orange;}
span > span {background: gold;}
span > span > span {background: yellow;}
<div class="container" contenteditable>
  text in an editable element
  <div>
    text in a nested div
  </div>
  <span><span><span>text in a deeply nested span</span></span></span></div>
Note how an insertion point can be attained by clicking above the first line or below the last due to the "hitbox" extension to the top and bottom of the container, respectively. Some other solutions fail to address this aspect!


Workaround 2 (invisible characters)

If retaining the contenteditable attribute on the inline-blocks is unavoidable, this workaround offers a feasible approach. By enveloping the inline-blocks with invisible characters (such as zero-width spaces), external clicks are shielded from affecting them. (GOTO 0's answer also utilizes this principle, though some issues were noted during recent evaluations).

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
  white-space: normal;
}
.input-container {white-space: nowrap;}
<div class="outside">
  <span class="input-container">&#8203;<div class="text-input" contenteditable>
    Input 1
  </div>&#8203;</span>
  <span class="input-container">&#8203;<div class="text-input" contenteditable>
    Input 2
  </div>&#8203;</span>
  <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
  </div>
</div>


Workaround 3 (javascript)

In scenarios mandating no changes to the markup structure, resorting to a JavaScript-driven solution could serve as a final recourse (inspired by this response). This solution involves toggling the contentEditable state to true upon clicking on the inline-blocks, reverting it back to false once focus is lost.

(function() {
  var inputs = document.querySelectorAll('.text-input');
  for(var i = inputs.length; i--;) {
    inputs[i].addEventListener('click', function(e) {
      e.target.contentEditable = true;
      e.target.focus();
    });
    inputs[i].addEventListener('blur', function(e) {
      e.target.contentEditable = false;
    });
  }
})();
div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}
<div class="outside">
    <div class="text-input">
      Input 1
    </div>
    <div class="text-input">
      Input 2
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to show that clicking here doesn't affect the contenteditable div
      but clicking the side does.
    </div>
</div>

Answer №2

After testing, I found that this issue only occurs in Chrome and Safari, indicating a possible Webkit-related problem.

It appears that there may be a faulty mechanism triggering text selection within the browser. If the divs are not set as contenteditable, clicking after the last character on the same line would initiate a text selection starting from the end of the line.

To address this issue, a workaround involves wrapping the contenteditable divs within a container element and styling the container with -webkit-user-select: none to make it unselectable.

A suggestion by Alex Char highlights that clicking outside the container could still trigger text selection at the beginning of the text inside it due to lack of static text between the first contenteditable div and its selectable ancestor container around it. One approach to mitigate this is inserting an invisible, zero-width span just before the initial contenteditable div to capture any unwanted text selection.

  • Reason for non-empty?: Empty elements are disregarded during text selection.
  • Reason for zero width?: To remain unseen...
  • Reason for invisibility?: To prevent copying the content to clipboard using commands like Ctrl+A, Ctrl+C.

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}
div.text-input-container {
  -webkit-user-select: none;
}
.invisible {
  visibility: hidden;
}
<div class="outside">
    <div class="text-input-container">
        <span class="invisible">&#8203;</span><div class="text-input" contenteditable="true">
            Input 1
        </div>
        <div class="text-input" contenteditable="true">
            Input 2
        </div>
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>

Even under normal circumstances, it's advisable to place adjacent inline-block elements in a separate container rather than alongside a block element (like the unrelated div) to prevent unexpected layout issues if the order of sibling elements changes.

Answer №3

If you find that display: inline-block is unnecessary, I suggest utilizing the float property instead. Check out this example.

Following your sample, the updated CSS code would look like:

div.text-input {
  display: block;
  background-color: black;
  color: white;
  width: 300px;
  float: left;
  margin-right: 10px;
}
div.unrelated {
  clear: both;
}

Answer №4

Prevent users from selecting text within a specific container... this solution should resolve the issue.

Here's an illustration:

* {
   -ms-user-select: none; /* IE 10+ */
   -moz-user-select: -moz-none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   user-select: none;
}

Answer №5

How can we incorporate jQuery into this scenario?

$(".outside").click(function(e){
    $(e.target).siblings(".text-input").blur();
    window.getSelection().removeAllRanges();
});

In real life situations, how do we handle clicks on children of siblings with contenteditable=true?

$(".outside").click(function(e){
    if ($(e.target).siblings(".text-input").length != 0){
        $(e.target).siblings(".text-input").blur();
        window.getSelection().removeAllRanges();
    } 
    else {
        $(e.target).parentsUntil(".outside").last().siblings(".text-input").blur();
        window.getSelection().removeAllRanges();
    }
});

window.getSelection().removeAllRanges();
"It is essential to remove all ranges after calling blur"

Answer №6

Although it may not be the most elegant approach, one possible solution is to enclose contenteditable elements within a table tag. This can prevent the focus from being automatically directed to these elements, as long as they are not directly clicked on.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issues with AJAX requests failing to fire with the latest version of jQuery

A small script I have checks the availability of a username in the database, displaying "taken" if it's already taken and "available" if it's not. The code below works perfectly with jQuery v1.7.2. However, I need to update it for jQuery v3.2.1. ...

Optimize your website by caching static pages and content using Node.js

When it comes to caching static content using nodejs, there seem to be two main methods that can be utilized: The first method involves using nodejs and utilizing the following code: app.use(express.static(path.join(__dirname, 'public'), { max ...

CSS Background color only applies to the lower section of the page

I'm attempting to create a social media button using an anchor tag and a fontawesome icon. My goal is to have the entire background colored black with CSS, but my previous attempts only resulted in the bottom half turning black. https://i.sstatic.net ...

Tips for retrieving data using ajax with servlets:

I am trying to send some data on the page to a servlet So I have created the following jQuery code to achieve this I am using all the data to construct a JSON string and send it directly to the servlet However, I am unsure how to retrieve the entire dat ...

What steps can I take to prompt this function to modify the value of my input?

After recently delving into the world of JavaScript, I decided to create a background color generator that randomly changes when a button is clicked. This simple project involves two input fields (color1 & color2) which receive random values upon clicking ...

The Kenburn zoom image effect fails to function

I want to implement a zoom effect on an image using the Ken Burns effect when it is clicked. However, the code I have written does not seem to be working as expected. Here is the code snippet: $(document).ready(function () { $('.kenburns').on( ...

The text emits a soft glow even without the cursor hovering over it

I need some assistance with my code that creates a glowing effect on text. Currently, the glow effect appears not only when the cursor hovers directly over the text but also when it hovers over the area around the text. Can someone explain why this is ha ...

I am experiencing issues with my AJAX file and it seems to be

After creating a basic AJAX application to retrieve data from a PHP file using an ajax.js file, everything was uploaded to the server. However, upon accessing localhost/mypage, the expected results did not occur due to unforeseen issues. Below is the HTML ...

"Conceal navigation options in Angular JS depending on whether a user is logged in or

I am currently working on an Ionic app that has side menus with items like home, login, dashboard, and logout. For the login functionality, I am using a server API to authenticate users. When a user is logged in, I want the side menu items to display as f ...

Limit the length of text using jQuery by specifying the maximum pixel width

Trying to utilize jQuery for a quick function that calculates the pixel width of a string on an HTML page and then shortens the string until it reaches a desired pixel width... Unfortunately, I'm encountering issues with this process as the text is n ...

What are the steps to design a curved trapezoid using CSS?

Is it possible to achieve this using CSS? This is the current progress I've made: div { -webkit-clip-path: polygon(0 79%, 60% 78%, 78% 100%, 0 100%); clip-path: polygon(0 79%, 60% 78%, 78% 100%, 0 100%); background:red; height:300px; ...

How can I improve my skills in creating efficient Angular routers?

In my Ionic project, I am dealing with an AngularJS routes file that contains a large number of routes, approximately a hundred. The structure is similar to the one provided below. (function() { 'use strict'; angular.module('applicatio ...

Adjust the overall quantity count whenever a row is removed from a live table

Within the code snippet below, users have the ability to select a color and add it to a table that adjusts dynamically. The quantity of each selected color is shown on every row, and the total quantity of all colors is displayed at the table's bottom. ...

Dynamic Dropdown Options in Angular 6 HTML Input Field

Looking to create an interactive HTML5 input field that guides the user with their input. Consider a scenario where the user is required to follow a specific grammar while entering text: rule: <expression> {<op><expression>} expression: ...

Prevent dragging functionality in ckeditor

Currently, I am encountering a problem with my ckeditor. The issue arises when I load a full page of HTML into the ckeditor. While it loads and displays correctly, I am seeking a way to restrict users from editing the alignment of the content, only allow ...

Determine the position of a nested object within a multidimensional array using JavaScript/TypeScript

My objective is to obtain the complete index of a complex object within a multidimensional array. For example, consider the following array: var arr = [ { name: "zero", children: null }, { name: "one", children: [ { ...

Guide to displaying database results in an HTML dropdown menu by utilizing AJAX technology

Greetings! As a newcomer to the realms of PHP and AJAX, I am currently on a quest to showcase the outcomes of a query within an HTML dropdown using AJAX. Let's delve into my PHP code: $pro1 = mysqli_query("select email from groups"); I made an attemp ...

Bootstrap's white navbar transitions seamlessly to a transparent state as it hovers

Recently, I've been tackling a design challenge by creating a navigation bar that changes opacity and size of the logo when scrolled past the header. Everything was going smoothly until I encountered an issue with the navbar overlapping a paragraph on ...

What is the best method to securely install a private Git repository using Yarn, utilizing access tokens without the need to hardcode them

My initial thought was to utilize .npmrc for v1 or .yarnrc.yml for v2/3/4, but in all scenarios, Yarn does not even attempt to authenticate with Github. nodeLinker: node-modules npmScopes: packagescope: npmAlwaysAuth: true npmAuthToken: my_perso ...

An array comparison function in JavaScript

Similar Question: Using jQuery to compare two arrays I'm looking for a Javascript or jQuery function that can check if one array contains all the values of another array. The second array may have more values than the first. The function should ...