JSFiddle - React, Tailwind, and code Playground

by Gust van de Wal

HTML

<p>I <span class="selectable" options="love|really like|really love">like</span> apples.</p>
<p>There's nothing <span class="selectable" options="in this world|in the universe">on earth</span> I eat more than apples.</p>

CSS

body {
  font-family: sans-serif;
}

.selectable {
  cursor: default;
}

.selectable:focus {
  outline: none;
}

.selectable div {
  position: relative;
  display: inline-block;
}

.selectable::before,
.selectable::after,
.selectable span {
  color: #F00;
}

.selectable.filled::before {
  content: '{';
  margin-right: .2em;
}

.selectable.filled::after {
  content: '}';
  margin-left: .2em;
}

.selectable ul {
  position: absolute;
  top: calc(100% + .2em);
  left: calc(-.4em - 1px);
  list-style: none;
  margin: 0;
  padding: 1px 0 0;
  overflow: hidden;
  background-color: #DDD;
  z-index: 1;
}

.selectable ul:not(:focus-within) {
  pointer-events: none;
  user-select: none;
  opacity: 0;
  transform: translateY(-5px);
  transition: opacity .25s ease, transform .4s ease;
}

.selectable ul:focus-within {
  transition: opacity .25s ease, transform .4s ease -.15s;
}

.selectable li {
  white-space: nowrap;
  padding: .4em;
  margin: 0 1px 1px;
  background-color: #FFF;
}

.selectable li:hover {
  background-color: #F7F7F7;
}

.selectable li.active {
  display: none;
}

JavaScript

$(function () {
  $('.selectable').each(function () {
    var $this = $(this)
    var list  = $this.attr('options').split('|')
    var text  = $this.text()
    $this
      .data('original', text)
      .html('<div><span>' + text + '</span><ul><li tabindex="0">' + list.join('</li><li tabindex="0">') + '</li></ul></div>')
  }).on('mousedown', function (e) {
    e.preventDefault()
    var $this   = $(this)
    var $target = $(e.target)
    var $focus  = $this.find(':focus')
    if ($focus.length) $focus.blur()
    else $this.find('li:not(.active)').eq(0).focus()
    if ($target.is('li')) changeSelection($this, $target)
  }).on('keydown', function (e) {
    var which = e.which
    if (which === 13) changeSelection($(this))
    else if (which === 40) $(this).find(':focus').next().focus()
    else if (which === 38) $(this).find(':focus').prev().focus()
  })
  function changeSelection ($this, $target) {
    $target = $target || $this.find(':focus')
    $target.blur()
    $this
      .one('transitionend', function () {
        $target.addClass('active').siblings().removeClass('active').last().focus().blur()
      })
      .addClass('filled')
      .find('span').text($this.data('original') + ' | ' + $target.text())
  }
})