explorer-like selection and move

by Stalk

HTML

<main>
  <section>
    <ul id="left">
      <li>Apples</li>
      <li>Butter</li>
      <li>Charlie</li>
      <li>Duff</li>
      <li>Edward</li>
    </ul>
  </section>
  <section class="mid">
    <button id="to-left">◀</button>
    <button id="to-right">▶</button>
  </section>
  <section>
    <ul id="right">
    </ul>
  </section>
</main>

CSS

body {
  height: 100vh;
  margin: 0;
  font-family: monospace;
  font-size: 20px;
}

main {
  display: flex;
}

section {
  width: calc(100% / 3);
  border: 1px solid lightgray;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

button {
  margin-top: 1em;
}

.selected {
  background: #9f9;
}

ul {
  list-style-type: none;
  padding: 1em;
}

li {
  cursor: pointer;
  user-select: none;
}

JavaScript

let left = document.getElementById('left'),
    right = document.getElementById('right');

selectableList(left, 'selected')
selectableList(right, 'selected')

document.getElementById('to-right')
  .addEventListener('click', () => move(left, right));
document.getElementById('to-left')
  .addEventListener('click', () => move(right, left));

function move(from, to) {
  from.querySelectorAll('.selected')
      .forEach(el => to.appendChild(el))
}

function selectableList(parent, cls) {
  let last = parent.firstElementChild
  parent.onmousedown = function(e) {
    let elems = Array.from(parent.children)
    if (e.target.matches('li')) {
      if (e.ctrlKey || e.metaKey) e.target.classList.toggle(cls);
      else if (e.shiftKey && parent.contains(last)) {
        let range = [elems.indexOf(last), elems.indexOf(e.target)].sort()
        elems.forEach((el, i) => 
          el.classList.toggle(cls, (i >= range[0] && i <= range[1]))
        )
      } else {
        elems.forEach(el => el.classList.remove(cls))
        e.target.classList.toggle(cls)
      }
      last = e.target
    }
  }
}