JSFiddle - React, Tailwind, and code Playground

author(s): Tao Xin

HTML

<script src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-0.12.4.nomodule.min.js"></script>
<script src="https://vanjs.org/code/diff.min.js"></script>

CSS

.row { display: flex; }

.column {
  width: 500px;
  margin: 2px;
}

.column textarea {
  box-sizing: border-box;
  width: 100%;
}

.add { background-color: #B5EFDB; }
.remove { background-color: #FFC4C1; }
.merged .remove { text-decoration: line-through; }

JavaScript

const {button, div, input, span, textarea} = van.tags

const autoGrow = e => {
  e.target.style.height = "5px"
  e.target.style.height = (e.target.scrollHeight + 5) + "px"
}

const Line = ({diff, skipAdd, skipRemove}) => div(
  {class: "column", style: "white-space: pre-wrap;"},
  diff.filter(d => !(skipAdd && d.added || skipRemove && d.removed)).map(d =>
    span({class: d.added ? "add" : (d.removed ? "remove" : "")}, d.value)),
)

const DiffLine = (oldLine, newLine, showMerged) => {
  const diff = Diff.diffWords(oldLine, newLine)
  return div({class: "row" + (showMerged ? " merged" : "")},
    showMerged ?
      Line({diff}) : [Line({diff, skipAdd: true}), Line({diff, skipRemove: true})],
  )
}

const DiffApp = () => {
  const oldTextDom = textarea({oninput: autoGrow, rows: 1})
  const newTextDom = textarea({oninput: autoGrow, rows: 1})
  const diff = van.state([])
  const showMerged = van.state(true)
  return div(
    div({class: "row"},
      div({class: "column"}, oldTextDom),
      div({class: "column"}, newTextDom),
    ),
    div({class: "row"},
      button({onclick: () => diff.val = Diff.diffLines(oldTextDom.value, newTextDom.value)},
        "Diff",
      ),
      input({type: "checkbox", checked: showMerged,
        oninput: e => showMerged.val = e.target.checked}),
      "show merged result"
    ),
    van.bind(diff, showMerged, (diff, showMerged) => {
      const resultDom = div()
      for (let i = 0; i < diff.length; ) {
        let line
        if (diff[i].added && diff[i + 1]?.removed) {
          line = DiffLine(diff[i + 1].value, diff[i].value, showMerged)
          i += 2
        } else if (diff[i].removed && diff[i + 1]?.added) {
          line = DiffLine(diff[i].value, diff[i + 1].value, showMerged)
          i += 2
        } else if (diff[i].added) {
          line = showMerged ? div({class: "merged add row"},
            div({class: "column", style: "white-space: pre-wrap;"}, diff[i].value),
          ) : div({class: "row"},
           ...