DIV innerText fills but not trigger the change event

Solution 1:

I've tried to replicated web.whatsapp.com. As mentioned above by myf for the solution need those attributes <div role="textbox" contenteditable=true> and the change event not fire in that case.

The input event works and for intercept the string we can to use event.target.textContent instead event.target.value.

document.addEventListener('DOMContentLoaded', function () {
  const searchBar = document.querySelector('.search-bar');
  const input = document.querySelector('.search-input');
  const clearButton = document.querySelector('.clear-button');

  const focusedField = () => {
    searchBar.classList.add('focused');
    input.focus();
  };

  const outSideClick = ev => {
    ev.stopPropagation();
    const isSearchBar = ev.target.closest('.search-bar');
    const isEmptyField = input.textContent.length;

    if (!isSearchBar && isEmptyField === 0) {
      searchBar.classList.remove('focused');
      document.removeEventListener('click', outSideClick);
    }
  };

  const showClearBtn = ev => {
    const isEmptyFiled = ev.target.textContent.length;
    console.log(ev.target.textContent);
    if (isEmptyFiled === 0) {
      clearButton.classList.remove('active');
      return;
    }
    clearButton.classList.add('active');
  };

  const clearText = () => {
    input.textContent = '';
    clearButton.classList.remove('active');
  };

  clearButton.addEventListener('click', clearText);
  input.addEventListener('input', showClearBtn);
  searchBar.addEventListener('mouseup', focusedField);
  document.addEventListener('mouseup', outSideClick);
});
*,
::after,
::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Open Sans', 'Helvetica Neue', sans-serif;
}

:root {
  --bg: hsl(201, 27%, 10%);
  --input-field: hsl(197, 7%, 21%);
  --font-color: hsl(206, 3%, 52%);
  --search-bar-height: 48px;
}

html {
  height: 100%;
}

body {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: var(--bg);
}

.search-bar {
  height: var(--search-bar-height);
  width: 350px;
  display: flex;
  align-items: center;
  position: relative;
  padding-inline: 1em 1.5em;
  overflow: hidden;
  background-color: var(--input-field);
  border-radius: 50px;
  z-index: 10;
}

.search-bar.focused .icon[data-icon='search'] {
  opacity: 0;
  transform: rotate(45deg);
}
.search-bar.focused .icon[data-icon='back'] {
  opacity: 1;
  transform: rotate(0deg);
}

.search-bar.focused .search-placeholder {
  display: none;
}

.search-button,
.clear-button {
  width: calc(var(--search-bar-height) / 2);
  height: calc(var(--search-bar-height) / 2);
  display: flex;
  position: relative;
  background-color: transparent;
  border: none;
  outline: none;
  transition: opacity 0.3s ease-in-out;
}

.icon {
  position: absolute;
  inset: 0;
  transition: all 0.3s ease-in-out;
  cursor: pointer;
}
.icon path {
  fill: var(--font-color);
}
.icon[data-icon='back'] {
  transform: rotate(-45deg);
  opacity: 0;
}
.clear-button {
  opacity: 0;
  pointer-events: none;
}
.clear-button.active {
  opacity: 1;
  pointer-events: initial;
}

.search-field {
  height: 2em;
  display: flex;
  margin-inline-start: 1em;
  flex-grow: 1;
  position: relative;
  overflow: hidden;
}

.search-placeholder,
.search-input {
  height: inherit;
  position: absolute;
  top: 3px;
  font-size: 1rem;
  color: var(--font-color);
  transition: all 0.3s ease-in-out;
  white-space: nowrap;
}

.search-placeholder {
  text-overflow: ellipsis;
  pointer-events: none;
  user-select: none;
}

.search-input {
  width: 100%;
  outline: none;
  border: 1px solid transparent;
  /* border transparent for caret visibility  */
}
<div class="search-bar" tabindex="1">
  <button class="search-button">
        <span class="icon" data-icon="search">
          <svg viewBox="0 0 24 24" width="24" height="24">
            <path
              fill="currentColor"
              d="M15.009 13.805h-.636l-.22-.219a5.184 5.184 0 0 0 1.256-3.386 5.207 5.207 0 1 0-5.207 5.208 5.183 5.183 0 0 0 3.385-1.255l.221.22v.635l4.004 3.999 1.194-1.195-3.997-4.007zm-4.808 0a3.605 3.605 0 1 1 0-7.21 3.605 3.605 0 0 1 0 7.21z"
            ></path>
          </svg>
        </span>
        <span class="icon" data-icon="back">
          <svg viewBox="0 0 24 24" width="24" height="24">
            <path
              fill="currentColor"
              d="M12 4l1.4 1.4L7.8 11H20v2H7.8l5.6 5.6L12 20l-8-8 8-8z"
            ></path>
          </svg>
        </span>
      </button>
  <div class="search-field">
    <div class="search-input" role="textbox" dir="ltr" tabindex="1" contenteditable="true"></div>
    <div class="search-placeholder">Search or start new chat</div>
  </div>
  <button class="clear-button">
        <span class="icon" data-icon="clear">
          <svg viewBox="0 0 24 24" width="24" height="24">
            <path
              fill="currentColor"
              d="M17.25 7.8L16.2 6.75l-4.2 4.2-4.2-4.2L6.75 7.8l4.2 4.2-4.2 4.2 1.05 1.05 4.2-4.2 4.2 4.2 1.05-1.05-4.2-4.2 4.2-4.2z"
            ></path>
          </svg>
        </span>
      </button>
</div>

Solution 2:

onChange event triggers only for these supported elements:

<input type="checkbox">, <input type="color">, <input type="date">,
<input type="datetime">, <input type="email">, <input type="file">,
<input type="month">, <input type="number">, <input type="password">,
 <input type="radio">, <input type="range">, <input type="search">,
 <input type="tel">, <input type="text">, <input type="time">,
 <input type="url">, <input type="week">, <select> and <textarea>

source: https://www.w3schools.com/jsref/event_onchange.asp

WhatsApp is built by ReactJS for web and ReactNative for mobile apps.

React has a event binder that reacts to certain changes and feeds back to the DOM.

If you're trying to recreate it using vanilla javascript, use the input tag