Нажатие за пределами контентных кавычек div фокусируется на нем?

По какой-то причине мне нужно использовать contenteditable div вместо обычного ввода текста для ввода текста. (для некоторых javascript-библиотек) Он отлично работает, пока не обнаружил, что когда я устанавливаю contenteditable div с помощью display: inline-block, он фокусируется на div, даже если я выхожу за пределы div!

Мне нужно, чтобы он фокусировался на div только тогда, когда пользователь щелкает прямо на div, но не вокруг него. В настоящее время я обнаружил, что когда пользователь щелкает в другом месте, а затем нажимает на позицию, которая является той же строкой, что и div, она фокусируется на ней.

Простой пример, чтобы показать проблему:

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 mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>

CSS

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

JSFiddle для отображения проблемы

Есть ли способ (CSS или javascript оба приемлемы), чтобы заставить браузер давать фокус только div, когда его щелкают, а не щелкая по той же строке?

P.S. Я заметил, что существует аналогичная проблема (ссылка на другой связанный пост), но ситуация немного отличается, и предоставленное решение не работает для меня.

Ответ 1

Объяснение (если вам все равно, перейдите к разделу "Обходные пути" ниже)

Когда вы нажимаете на редактируемый элемент, браузер помещает курсор (aka точка вставки) в ближайшем текст node, который находится внутри элемента с щелчком, в той же строке, что и ваш клик. Текст node может быть либо непосредственно внутри элемента с кликом, либо в одном из его дочерних элементов. Вы можете проверить это, щелкнув по фрагменту кода ниже.

.container {width: auto; padding: 30px; background: tan;}
.container * {margin: 4px; padding: 4px;}
div {width: 50%; background: lightgreen;}
span {background: orange;}
span > span {background: gold;}
span > span > span {background: yellow;}
<div class="container" contenteditable>
  text
  <div>
    text in a div
  </div>
  <span><span><span>text in spans</span></span></span></div>
Notice that you can get an insertion point by clicking above the first line or below the last. Some of the other answers don't account for this!

Ответ 2

Я смог воспроизвести это поведение только в Chrome и Safari, что говорит о том, что это может быть проблемой, связанной с Webkit.

Трудно сказать, что происходит без проверки кода, но мы можем хотя бы подозревать, что проблема заключается в некотором неисправном механизме, который запускает выбор текста в браузере. Для аналогии, если div не были приемлемыми, щелчок в одной строке текста после последнего символа вызовет выбор текста, начинающийся в конце строки.

Обходной путь состоит в том, чтобы обернуть contenteditable div в элемент контейнера и создать контейнер с -webkit-user-select: none, чтобы сделать его невыбираемым.

Как отмечает Alex Char в комментарии, это не будет препятствовать щелчку мыши за пределами контейнера, чтобы вызвать выбор в начале текста внутри него, поскольку нет статического текста между первым contenteditable div и контейнер (предсказываемый) предка вокруг него. Вероятнее всего, более элегантные решения, но способ преодолеть эту проблему состоит в том, чтобы вставить невидимый, непустой диапазон текста нулевой ширины непосредственно перед первым contenteditable div, чтобы захватить нежелательный выбор текста.

  • Почему не пустой?: Потому что пустые элементы игнорируются при выборе текста.
  • Почему нулевая ширина?: Потому что мы не хотим ее видеть...
  • Почему невидимо? Потому что мы не хотим, чтобы содержимое копировалось в буфер обмена, скажем 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>

Ответ 3

Если вам не нужно использовать display: inline-block, я бы рекомендовал использовать float. Вот пример.

На основе вашего примера новый CSS будет выглядеть следующим образом:

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

Ответ 4

Отключить выбор текста в контейнере... должен исправить это.

Например:

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

Ответ 5

Как насчет небольшого jQuery?

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

И если IRL вам нужно будет зачислить клики на дочерях contenteditable=true siblings:

$(".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(); "Уловка заключается в том, чтобы удалить все диапазоны после размытия вызова"