1

I have a form in which there are input elements of type text that may contain text starting with @, now what I want to achieve is to highlight the partial text inside the input elements that starts with @. I have seen other solutions like using div with contenteditable and other solutions like using editor that does work but they changes the actual content with the HTML and this is not what I don't want to use.

Now I have noticed chrome implements find and highlight the text as shown in below image and that seems I can modifify its logic with my requirements,

Highlight on search

other example

Is there any hints or solutions ? Please help me with this case.

ABC
  • 31
  • 6
  • Do you want to style them a certain way, or are you trying to actually select the text (.e.g. to copy it)? – Makaze Jun 30 '23 at 05:55
  • I want to style them in a certain way like highlighting them, see a second image like that. – ABC Jun 30 '23 at 05:57
  • https://stackoverflow.com/questions/66899473/highlight-specific-words-in-textarea-angular-8 – Eliseo Jun 30 '23 at 06:39
  • Duplicate! [Highlighting text within an input field](https://stackoverflow.com/questions/67119347/highlighting-text-within-an-input-field) – Nebojsa Nebojsa Jun 30 '23 at 06:39
  • Sorry @NebojsaNebojsa, but see this line in a question -> I have noticed Chrome implements find and highlight the text as shown in the below image and that seems I can modify its logic with my requirements. I think it is possible to do it in the way I mentioned in the question. So this way the question may seem similar but is different, also please refer to the images. – ABC Jun 30 '23 at 06:59
  • @NebojsaNebojsa, all the solutions to that question are contenteditable that may fulfill the requirement of that question nor that question mentioned that it do not want to use contenteditable as I mentioned, this is because using contenteditable changes the actual content of input text with html tags and that is what I dont want, so again I can say my question may seem similar but is different, please help me with this case. :) – ABC Jun 30 '23 at 07:08

2 Answers2

2

You could do something like the following:

function updateSpanText() {
  const input = document.querySelector(".input");
  const highlightedText = document.querySelector(".highlighted-text");
  const highlightedTextContent = input.value.replace(/@(\w+)/g, '<span class="highlight">@$1</span>');
  highlightedText.innerHTML = highlightedTextContent;
}

function onFocus() {
  const highlightedText = document.querySelector(".highlighted-text");
  highlightedText.style.display = 'none';
}

function onBlur() {
  const highlightedText = document.querySelector(".highlighted-text");
  highlightedText.style.display = 'inline-block';
}
* {
  box-sizing: border-box;
}

.highlight {
  background-color: yellow;
}

.input-container {
  position: relative;
  display: inline-block;
  width: 200px;
  line-height: 22px;
}

.highlighted-text {
  position: absolute;
  left: 4px;
  top: 3px;
  background-color: white;
  pointer-events: none;
  font-family: "Helvetica Neue";
  font-size: 14px;
  line-height: 22px;
}

.input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  font-family: "Helvetica Neue";
  font-size: 14px;
  line-height: 22px;
}
<div class="input-container">
  <input class="input" type="text" onblur="onBlur()" onfocus="onFocus()" onchange="updateSpanText()" />
  <span class="highlighted-text"></span>
</div>

The idea is that you keep track of the input-value and once you are done editing, you layer the rendered text on top of the input. When you are actually editing, you just display the input itself.

With pointer-events: none we can prevent the span from catching events so all events are received by the input.

F. Müller
  • 3,969
  • 8
  • 38
  • 49
  • Yes, this seems one more good way to do it, let me try this too. Do you think we can also do something like a browser do it finds and highlight including the input tag? – ABC Jun 30 '23 at 07:28
  • No, we cannot do it with simple input elements. It is not meant for that use-case. If you want more advanced features use a textarea instead. But you can style the textarea to look more or less like a regular input so ... You can also use WebComponents and create your own input element but I'd not recommend that. – F. Müller Jun 30 '23 at 07:43
  • @ABC You may want to checkout https://developer.mozilla.org/en-US/docs/Web/CSS/::highlight but this is a draft and it does not work on all browsers. – F. Müller Jun 30 '23 at 08:49
0

You cannot style substrings of a true text field. If you want to keep the input for functionality reasons but show the user a styled field, you can have a contenteditable on top with a hidden input on the bottom and bind them so that the raw text gets submitted in the input but your contenteditable is just for show.

Example case:

function setCaret(el, childNode, index) {
  var range = document.createRange();
  var sel = window.getSelection();

  range.setStart(childNode, index);
  range.collapse(true);

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

function getSelectedElement() {
  var sel = window.getSelection();
  var range = sel.getRangeAt(0);
  var tag = range.startContainer.parentNode;
  
  console.log(tag);
  
  return tag;
}

document.querySelector('#name-editor').addEventListener('keydown', function(event) {
  var $self = document.querySelector('#name-editor');

  if (event.key == '@') {
    event.preventDefault();
    $self.appendChild(document.createElement('span')).className = 'highlight';
    $self.lastChild.appendChild(document.createTextNode('@'));
    setCaret($self, $self.lastChild, 1);
  }
  
  if (event.keyCode == 32 && getSelectedElement().className.indexOf('highlight') > -1) {
    event.preventDefault();
    $self.appendChild(document.createElement('span'));
    $self.lastChild.innerHTML = '&nbsp;';
    setCaret($self, $self.lastChild, 1);
  }
});

document.querySelector('#name-editor').addEventListener('keyup', function(event) {
  document.querySelector('#name-field').value = this.textContent.trim();
  console.log(document.querySelector('#name-field').value);
});
.highlight {
  display: inline-block;
  padding: 5px;
  background-color: rgba(0, 0, 200, .3);
  border-radius: 3px;
}

[contenteditable] {
  border: 1px solid rgba(0,0,0.1);
  padding: 5px;
  display: inline-block;
  width: 10em;
}
<form>
  <input id="name-field" type="text" name="name" style="display: none;" />
  Name: <div id="name-editor" contenteditable="true"></div>
</form>
Makaze
  • 1,076
  • 7
  • 13