JSFiddle - React, Tailwind, and code Playground

by jrj2211

HTML

<script type="module" src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js"></script>

<div class='table'>
  <div class='row' >
    <div class='handle' draggable=true><ion-icon name="reorder-two-outline"></ion-icon></div>
    <div class='id'></div>
    <div class='name'>Item 1</div>
  </div>
  <div class='row' >
    <div class='handle' draggable=true><ion-icon name="reorder-two-outline"></ion-icon></div>
    <div class='id'></div>
    <div class='name'>Item 2</div>
  </div>
  <div class='row' >
    <div class='handle' draggable=true><ion-icon name="reorder-two-outline"></ion-icon></div>
    <div class='id'></div>
    <div class='name'>Item 3</div>
  </div>
  <div class='row' >
    <div class='handle' draggable=true><ion-icon name="reorder-two-outline"></ion-icon></div>
    <div class='id'></div>
    <div class='name'>Item 4</div>
  </div>
</div>

CSS

.table {
  counter-reset: line-item-counter;
  display: grid;
  grid-template-columns: auto auto 1fr;
  background: black;
  gap: 1px;
  border: 1px solid black;
}


.table .row {
 display: contents;
}

.table .row.dragging > * {
  background: red;
}

.table .row > * {
  background: white;
  padding: 10px;
}

.table .row .handle {
  cursor: 'move';
}

.table .row .id::before {
  counter-increment: line-item-counter;
  content: counter(line-item-counter);
}

JavaScript

const table = document.querySelector('.table');
let dragging = null;
table.addEventListener('dragstart', (evt) => {
  const row = evt.target.closest('.row');
  dragging = row;
  dragging.classList.add('dragging');
  
  const ghost = document.createElement('div');
  //evt.dataTransfer.setDragImage(ghost, 0, 0);
});

table.addEventListener('dragend', (evt) => {
  dragging.classList.remove('dragging');
  dragging = null;
});

table.addEventListener('dragover', (evt) => {
	evt.preventDefault();
	const rows = [...table.querySelectorAll(`.row:not(.dragging)`)];
  const y = evt.clientY;
  const closest = getClosest(rows, y);
  
  if(closest.element) {
  	table.insertBefore(dragging, closest.element);
  } else {
  	table.append(dragging);
  }
});

function getClosest(rows, y) {
	return rows.reduce((closest, element) => {
  	const box = getBounds(element);
    const offset = y - box.top - (box.height / 2);
    if (offset < 0 && offset > closest.offset) {
    	return { offset, element };
    }
    return closest;
  }, {  offset: Number.NEGATIVE_INFINITY });
}

function getBounds(row) {
	const first = row.firstElementChild.getBoundingClientRect();
  const last = row.lastElementChild.getBoundingClientRect();
  return { 
		top: first.top,
    right: last.right,
  	bottom: last.bottom,
    left: first.left,
    height: Math.abs(last.bottom - first.top),
 		width: Math.abs(last.right - first.left),
   	x: first.x,
    y: first.y
  }
}