JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<div class="container">
   
   <div>
      <h3>Sortable List 1</h3>
      <ul id="list-1" class="dock sortable" data-sortable="true" data-denies="[3]" data-tag="2">
         <li id="item-1" class="draggable-item">Item 01 &nbsp;-&nbsp; <a href="#1">Link 01</a></li>
         <li id="item-2" class="draggable-item">Item 02 &nbsp;-&nbsp; <a href="#2">Link 02</a></li>
         <li id="item-3" class="draggable-item">Item 03 &nbsp;-&nbsp; <a href="#3">Link 03</a></li>
         <li id="item-4" class="draggable-item longer">Item 04 &nbsp;-&nbsp; <a href="#4">Link 04</a></li>
         <li id="item-5" class="draggable-item larger">Item 05 &nbsp;-&nbsp; <a href="#5">Link 05</a></li>
         <li id="item-6" class="draggable-item">Item 06 &nbsp;-&nbsp; <a href="#6">Link 06</a></li>
      </ul>
      <button id="sbtn-1" type="button">Serialize</button>
   </div>
   
   <div>
      <h3>Non-sortable List 2</h3>
      <ul id="list-2" class="dock sortable" data-max-items="2" data-can-undock="false" data-accepts="[2]" data-tag="1">
         <li id="item-7" class="draggable-item">Item 07 &nbsp;-&nbsp; <a href="#7">Link 07</a></li>
         <li id="item-8" class="draggable-item">Item 08 &nbsp;-&nbsp; <a href="#8">Link 08</a></li>
         <li id="item-9" class="draggable-item">Item 09 &nbsp;-&nbsp; <a href="#9">Link 09</a></li>
         <li id="item-10" class="draggable-item">Item 10 &nbsp;-&nbsp; <a href="#10">Link 10</a></li>
         <li id="item-11" class="draggable-item">Item 11 &nbsp;-&nbsp; <a href="#11">Link 11</a></li>
         <li id="item-12" class="draggable-item">Item 12 &nbsp;-&nbsp; <a href="#12">Link 12</a></li>
      </ul>
      <button id="sbtn-2" type="button">Serialize</button>
   </div>

   
</div>





<div class="draggable-item longer">Item 07</div>
<div class="draggable-item longer" style="position:fixed; top:50px; right:50px;">Item 07</div>

<div style="width:50vw; height:100vh; overflow:auto;">
   <div>
      <h3>Sortable List 3</h3>
      <ul id="list-2"...

SCSS

*, *:before, *:after {
   box-sizing: border-box;
}

html, body {
   height: 100%;
   margin:0;
   padding:0;
}

body {
   overflow: hidden; 
   overflow-y: scroll;
   font-family: Arial, sans-serif;
   background: rgb(252,255,244);
   background: radial-gradient( ellipse at center, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 50%,rgba(179,190,173,1) 100% );
}

.container {
   display: flex;
   flex-direction: row;
   align-items: center;
   justify-content: space-around;
   width: 100%;
   height: 100%; 
   max-width: 800px; 
   margin: 0 auto;
   
   div {
      flex-basis: 40%; 
      
      h3 {
         font-weight: bold; 
         font-size: 150%; 
         line-height: 1em; 
      }
   }
}

ul.sortable {
   display: block; 
   list-style: none; 
   background: #e0e0e0;
   border-radius: 4px;
   box-shadow: 0 0 40px rgba(0,0,0,0.4);
   padding: 5px;
   margin:0;
   
   & > li {
      display: inline-block;
      margin: 1px; 
      padding: 10px;
      border-radius: 4px;
      cursor: move;
      background: #fff;
      &.larger {
         width:200px;
      }
      &.longer {
         height:100px;
      }
      &:hover {
         background: #ffffe0;
      }
      &.active {
         background: #ccc;
      }
      &.dragging {
         background: #e0ffff;
         box-shadow: 0 4px 20px rgba(0,0,0,0.5);
         opacity: 0.5;
      }
   }
}

div.draggable-item {
  background:red;
  width:100px;
  color:white;
  padding:5px;
  display:inline-block;
}

.draggable-item, .dock {
  transition: background-color 400ms ease-out, border-color 400ms ease-out, color 400ms ease-out;
}

.dock {
  background: #eeeeee;
  &.active {
    background: #e0ffff;
  }
  &.denied {
    background: red;
  }
}

.dock-bottom {
  position:fixed;
  bottom:0;
  height:60px;
  left:0;
  right:0;
  display: inline-flex;
  flex-direction: row-reverse;
  justify-content: flex-start;
  align-items: flex-end;
}

JavaScript

// get position of mouse/touch in relation to viewport 
const _w = window,
	_b = document.body,
	_d = document.documentElement;

const getPointBad = function(e) {
	return {x: (e.pageX || e.clientX || 0), y:e.pageY || e.clientY || 0};
};

const getPoint = function( e ) {

  var scrollX = Math.max( 0, _w.pageXOffset || _d.scrollLeft || _b.scrollLeft || 0 ) - ( _d.clientLeft || 0 ), 
      scrollY = Math.max( 0, _w.pageYOffset || _d.scrollTop || _b.scrollTop || 0 ) - ( _d.clientTop || 0 ), 
      pointX  = e ? ( Math.max( 0, e.pageX || e.clientX || 0 ) - scrollX ) : 0,
      pointY  = e ? ( Math.max( 0, e.pageY || e.clientY || 0 ) - scrollY ) : 0; 
      
      return { x: pointX, y: pointY }; 
};

const getOffset = function(el) {
	    const rect = el.getBoundingClientRect(),
	    scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
	    scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      if (el)
	    return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
};

const getAbsoluteOffset = function (el) {
	const doc = document,
		win = window,
		body = doc.body;

		// pageXOffset and pageYOffset work everywhere except IE <9.
		let offsetX = win.pageXOffset !== undefined ? win.pageXOffset :
			(doc.documentElement || body.parentNode || body).scrollLeft,
			offsetY = win.pageYOffset !== undefined ? win.pageYOffset :
			(doc.documentElement || body.parentNode || body).scrollTop,

		rect = el.getBoundingClientRect();

	if (el !== body) {
		let parent = el.parentNode;
		// The element's rect will be affected by the scroll positions of
		// *all* of its scrollable parents, not just the window, so we have
		// to walk up the tree and collect every scroll offset. Good times.
		while (parent !== body) {
			offsetX += parent.scrollLeft;
			offsetY += parent.scrollTop;
			parent = parent.parentNode;
		}
	}

	return {
		bottom: rect.bottom + offsetY,
		height: rect.height,
		left: rect.left + offsetX,
		right: rect.right +...