JSFiddle - React, Tailwind, and code Playground

by alexb

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.min.js"></script>
<div id="list">
  <div id="controls">
    <button id="add">Add</button>
    <button id="clear">Clear</button>
  </div>
  <div id="items"></div>
</div>
<div id="trackpad">
  <div id="coords"></div>
  Click to set coordinates
</div>
<div id="log">
  <div>Both mutate the list and set coordinates at least once to start logging</div>
</div>

CSS

* { box-sizing: border-box; }
body { display: flex; flex-flow: column; height: 100vh; }
body > * { flex: 1 0 0; }
#list { display: flex; align-items: center; padding: 0 2em; }
#controls { display: flex; flex-flow: column }
#items { display: flex; }
#items > * { background: #693; width: 2em; height: 2em; margin-left: 1em; line-height: 2; text-align: center; }
#trackpad {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rebeccapurple;
  color: white;}
#coords { position: absolute; top: 0; left: 0; }
#log { flex-grow: 2; background: lightgray; overflow-y: scroll; font-family: monospace; }
#log > * { margin: 0.5em }

JavaScript

// This stuff could also be implemented as RxJS streams.
add.addEventListener('click', e => {
	const el = document.createElement('div')
  el.innerText = Math.ceil(Math.random() * 100);
	items.appendChild(el);
});
clear.addEventListener('click', e => { items.innerHTML = ''; });
trackpad.addEventListener('mousemove', e => { coords.innerText = `${e.x}, ${e.y}`; })

// RxJS has a built-in way to create a stream from MutationObserver in
// the rxjs-dom package, but I can't get that to import to JSFiddle.
function createMutationStream(target, options) {
	return rxjs.Observable.create(rxjsObserver => {
  	const domMutObs = new MutationObserver(mutations => {
  		rxjsObserver.next(mutations);
  	});
    domMutObs.observe(target, options);
    return () => domMutObs.disconnect();
  });
}

// Helper function.
function describeMutations(mutations) {
  return mutations
    .map(mut =>
    	mut.addedNodes.length ?
      	'added ' + [...mut.addedNodes].map(el => el.innerText).toString() :
        'cleared')
    .join(', ');
}

// Create two discrete streams of events.
const click$ = rxjs.fromEvent(trackpad, 'click');
const listUpdate$ = createMutationStream(items, { childList: true });

// Use the combineLatest operator to create a stream of the LATEST
// values from each stream when EITHER stream emits something.
rxjs.combineLatest(click$, listUpdate$).subscribe(([mouse, mutations]) => {
  log.innerHTML += `<div>
    last click: ${mouse.x}, ${mouse.y},
    last mutation: ${describeMutations(mutations)}
  </div>`;
  log.scrollTop = log.scrollHeight;
});