JSFiddle - React, Tailwind, and code Playground

by Josh Pullen

HTML

<ul class="script">
    <li class="hat" style="background:#F24927">&lt;!DOCTYPE html&gt;</li>
    <ul class="c-wrapper">
        <li class="c-header" style="background:#F0750A">&lt;body&gt;</li>
        <ul class="c-content" style="background:#F0750A">
            <li class="stack" style="background:#DBB314">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#6FB317">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#1A9682">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#1C4ED9">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#6137BD">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#C844CF">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
            <li class="stack" style="background:#B5316C">&lt;img src="<span contenteditable='true' class="script-input" value="bring-back-breadfish.gif"></span>"&gt;</li>
        </ul>
        <li class="c-footer" style="background:#F0750A">&lt;/body&gt;</li>
    </ul>
</ul>

CSS

@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:400,700);
 body {
    background-color: gray;
}
* {
    font-family:'Source Code Pro';
    color:white;
    white-space: nowrap;
    /* Don't let text to "wrap" to a new line (blocks would look ugly that way) */
    -webkit-user-select: none;  /* Chrome all / Safari all */
    -moz-user-select: none;     /* Firefox all */
    -ms-user-select: none;      /* IE 10+ */
    user-select: none;          /* Likely future */
    
    cursor: default;
}
.script-input {
    font-family:'Source Code Pro';
    padding:0 5px;
    min-width: 25px;
    border-radius:999px;
    border:none;
    outline:none;
    background-color:white;
    color: black;
    display: inline-block;
    text-align: center;
}
.hat {
    display:table;
    /* Using "block" would result in 100% width, and "inline-block" would result in everything on same line */
    background:#C6E44D;
    padding:10px;
    position:relative;
    border-radius:0px 5px 5px 5px;
}
.hat:after {
    background:inherit;
    display:block;
    content:' ';
    width:80%;
    height:80%;
    position:absolute;
    top:-35%;
    left:0px;
    border-radius:50%;
    z-index:-999;
}
.stack {
    display:table;
    background:#914DE4;
    padding:10px;
    position:relative;
    border-radius:5px;
    border-left: 1px solid #A9A9A9;
}
.c-wrapper {
    padding:0px;
    display:table;
    border-left: 1px solid #A9A9A9;
}
.c-header, .c-footer {
    display:table;
    background:#E44D4D;
    padding:10px;
    position:relative;
    border-radius:5px;
    min-width:40px;
    min-height:20px;
}
.c-content {
    background:#E44D4D;
    padding:3px 0px 3px 25px;
    margin:-3px 0px -3px 0px;
    width:4px;
    overflow-x: visible;
    min-height: 40px;
}
.c-content:empty:after {
    width:0px;
    height:40px;
    content:' ';
    display:block;
}
.c-header {
    border-top: 1px solid #A9A9A9;
}
.c-footer {
    border-bottom: 1px solid #A9A9A9;
}
.drop-area {
   ...

JavaScript

/* PLEASE SEE
    https://github.com/ElementalCode/Elemental/tree/draggy-snappy
 * FOR LATEST */

var selected = null, // Object of the element to be moved
    x_pos = 0, y_pos = 0, // Stores x & y coordinates of the mouse pointer
    x_elem = 0, y_elem = 0; // Stores top, left values (edge) of the element

var SNAP_CLASSES = '.stack, .c-header, .c-footer, .hat';

function isDescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}

function closestElem(elem, offset, initial) { //elem is nodelist, offset is own offset
    var el = null,
        elOffset,
        x = offset.x, //x is my own offset
        y = offset.y, //y is my own offset
        distance,
        dx, dy,
        minDistance;
    elem.each( function(item) {
        if(item == selected) {return false;} // I added this -NN
        elOffset = getOffset(item); //returns object with offsets

        //we'll worry about inside c-blocks later:
        /*if (
        (x >= elOffset.left)  && (x <= elOffset.right) &&
        (y >= elOffset.top)   && (y <= elOffset.bottom)
        ) {
            el = item;
            return false;
        }*/
        
        //let's only test the bottom-left corner
        dx = elOffset.left - x;
        dy = elOffset.bottom - y;
        distance = Math.sqrt((dx*dx) + (dy*dy)); //dist to each corner
        if (initial != item && !isDescendant(initial, item) && (minDistance === undefined || distance < minDistance)) {
            minDistance = distance;
            el = item;
        }
    });
    return el;
}

function getOffset( elem ) {
    var offsetLeft = (function(elem) {
        var offsetLeft = 0;
        do {
          if ( !isNaN( elem.offsetLeft ) )
          {
              offsetLeft += elem.offsetLeft;
          }
        } while( elem = elem.offsetParent );
        return offsetLeft;
    })(elem);
...