JSFiddle - React, Tailwind, and code Playground

by Vladimir Kutepov

HTML

<ul>
  <li class="source">#1 source</li>
  <li class="source">#2 source</li>
  <li class="source">#3 source</li>
</ul>
<ul>
  <li class="target">#4 target</li>
  <li class="target">#5 target</li>
  <li class="target">#6 target</li>
</ul>
<ul>
  <li class="source target">#7 source and target</li>
  <li class="source target">#8 source and target</li>
  <li class="source target">#9 source and target</li>
</ul>
<button type="button">Destroy</button>

CSS

ul {
  background: LightYellow;
  padding: 0.5rem 2rem;
}
li {
  border: 1px solid Transparent;
}
.dragging {
  pointer-events: none;
  background: Orange;
}
.highlight {
  border-color: Orange;
  border-style: dotted;
}

JavaScript

class List {
  constructor(el) {
    this.el = el;
    this.handleMouseDown = this.handleMouseDown.bind(this);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.handleMouseUp = this.handleMouseUp.bind(this);
    this.el.addEventListener('mousedown', this.handleMouseDown);
  }
  handleMouseDown(event) {
    const el = event.target;
    if (this.el.contains(el) && el.classList.contains('source')) {
      event.preventDefault(); // prevent native dnd
      this.source = el;
      this.startX = event.pageX;
      this.startY = event.pageY;
      window.addEventListener('mousemove', this.handleMouseMove);
      window.addEventListener('mouseup', this.handleMouseUp);
      window.addEventListener('mouseover', this.handleMouseOver);
      window.addEventListener('mouseout', this.handleMouseOut);
    }
  }
  handleMouseMove(event) {
    const offsetX = event.pageX - this.startX;
    const offsetY = event.pageY - this.startY;
  	this.source.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
    this.source.classList.add('dragging');
  }
  handleMouseUp(event) {
    window.removeEventListener('mousemove', this.handleMouseMove);
    window.removeEventListener('mouseup', this.handleMouseUp);
    window.removeEventListener('mouseover', this.handleMouseOver);
    window.removeEventListener('mouseout', this.handleMouseOut);
    const el = event.target;
    if (this.source !== el && el.classList.contains('target')) {
      el.parentNode.insertBefore(this.source, el);
      el.classList.remove('highlight');
    }
    this.source.classList.remove('dragging');
    this.source.style.transform = '';
    this.source = null;
  }
  handleMouseOver(event) {
    const el = event.target;
    if (this.source !== el && el.classList.contains('target')) {
      el.classList.add('highlight');
    }
  }
  handleMouseOut(event) {
    const el = event.target;
    el.classList.remove('highlight');
  }
  destroy() {
    window.removeEventListener('mousemove',...