JSFiddle - React, Tailwind, and code Playground

by Imri Paloja

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">


<div class="container">

<div id="editor-content" spellcheck="false" contenteditable="true">
  <p draggable="true">Paragraph 1 – Lorem ipsum dolor sit amet.</p>
  <p draggable="true">Paragraph 2 – Consectetur adipiscing elit.</p>
  <p draggable="true">Paragraph 3 – Sed do eiusmod tempor.</p>
  <p draggable="true">Paragraph 4 – Incididunt ut labore et dolore.</p>
  <p draggable="true">Paragraph 5 – Magna aliqua.</p>
</div>

</div>

CSS

html,
body {
  color: #454545;
}

.container {
  margin-top: 5% !important;
}

  /* ------------------------------------------------------------------
     Layout of the editable area
     ------------------------------------------------------------------ */
  #editor-content {
    width: 600px;
    margin: 2rem auto;
    padding: 1rem;
    border: 2px solid #ddd;
    border-radius: 6px;
    background: #fafafa;
    line-height: 1.6;
    font-family: Arial, sans-serif;
  }

  #editor-content p {
    position: relative;               /* anchor for the handle */
    margin: 0.5rem 0;
    padding-right: 2rem;              /* space for the handle */
    cursor: text;                     /* normal text cursor inside the paragraph */
  }

  /* ------------------------------------------------------------------
     The draggable handle – a tiny <span> that lives at the right edge
     ------------------------------------------------------------------ */
  .drag-handle {
    position: absolute;
    top: 50%;
    right: 0.4rem;
    transform: translateY(-50%);
    width: 1.2rem;
    height: 1.2rem;
    cursor: grab;                     /* shows the “grab” cursor */
    display: inline-block;
  }

  .drag-handle:after {
    /* This draws a simple “≡” (three bars) icon */
    content: "\2261";
    font-size: 1.2rem;
    color: #777;
    line-height: 1;
    display: block;
    text-align: center;
  }

  .drag-handle.dragging {
    opacity: 0.5;
    cursor: grabbing;
  }

  /* Placeholder that appears while dragging */
  .placeholder {
    height: 0;
    margin: 0.5rem 0;
    border-top: 2px dashed #4a90e2;
  }

JavaScript

/* ==============================================================
   1️⃣ Helper: add a draggable handle to a single <p>
   ============================================================== */
function addHandleToParagraph(p) {
  // Avoid adding duplicate handles
  if (p.querySelector('.drag-handle')) return;

  const handle = document.createElement('span');
  handle.className = 'drag-handle';
  handle.setAttribute('draggable', 'true');
  p.appendChild(handle);
}

/* ==============================================================
   2️⃣ Initialise: add handles to all existing paragraphs
   ============================================================== */
function initHandles() {
  const container = document.getElementById('editor-content');
  container.querySelectorAll('p').forEach(addHandleToParagraph);
}

/* ==============================================================
   3️⃣ MutationObserver – watch for new <p> elements
   ============================================================== */
function observeNewParagraphs() {
  const container = document.getElementById('editor-content');

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        // Directly added <p>
        if (node.nodeType === Node.ELEMENT_NODE && node.matches('p')) {
          addHandleToParagraph(node);
        }

        // Or a wrapper that contains <p> children
        if (node.nodeType === Node.ELEMENT_NODE) {
          node.querySelectorAll('p').forEach(addHandleToParagraph);
        }
      });
    });
  });

  observer.observe(container, { childList: true, subtree: true });
}

/* ==============================================================
   4️⃣ Drag‑and‑Drop logic (delegated on the container)
   ============================================================== */
function enableDnD() {
  const container = document.getElementById('editor-content');
  let...