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
}
}