JSFiddle - React, Tailwind, and code Playground

by john_s

HTML

<div id="editor-container">
    <div id="editor" tabindex="1"></div>
</div>

CSS

#editor-container {
    position: relative;
    margin: 0;
    padding: 0;
    border: 1px solid black;
    width: 412px;
    height: 72px;
}
#editor {
    position: relative;
    margin: 0;
    border: 2px solid white;
    width: 400px;
    height: 60px;
    padding: 5px;
}
#editor:focus {
    border: 2px dashed black;
}
.caret {
    padding-top: 12px;
    display: inline-block;
    text-align: center;
    width: 12px;
    height: 50px;
    position: absolute;
    left: 0;
    top: 0;
}
.letter {
    display: inline-block;
    text-align: center;
    border: 1px solid black;
    width: 20px;
    height: 50px;
    position: absolute;
    left: 0;
    top: 0;
}
.letter>.letter-char {
    display: block;
    font-size: 140%;
}
.letter>.letter-hidden {
    display: block;
}

JavaScript

var LETTER_TEMPLATE = '<div class="letter">'
    + '<div class="letter-char"></div>'
    + '<div class="letter-hidden">'
    + '<input type="checkbox"/>'
    + '</div>'
    + '</div>';
var CARET_TEMPLATE = '<div class="caret">&gt;</div>';

var EDITOR_MAX = 16;

var $editor = $('#editor');

var letters = [];
var caretPos = 0;

function createLetterBox(letter, index) {
    var $letter = $(LETTER_TEMPLATE);
    $letter.data('index', index);
    $letter.find('.letter-char').text(letter.char);
    $letter.find('.letter-hidden').children('input:checkbox').prop('checked', letter.hidden);
    return $letter;
}

function updateDisplay() {
    var blocks = [];
    var i = 0;
    var left = 0;
    while (i < caretPos) {
        blocks.push(createLetterBox(letters[i], i).css('left', left + 'px'));
        i++;
        left += 22;
    }
    blocks.push($(CARET_TEMPLATE).css('left', left + 'px'));
    left += 10;
    while (i < letters.length) {
        blocks.push(createLetterBox(letters[i], i).css('left', left + 'px'));
        i++;
        left += 22;
    }
    $editor.html(blocks);
}

$editor.on('click', '.letter', function(e) {
    if (!$(e.target).is('input')) {
        caretPos = $(this).data('index');
        updateDisplay();
    }
});

$editor.on('change', '.letter>.letter-hidden>input:checkbox', function() {
    var $checkbox = $(this);
    console.log('change: ' + $checkbox.prop('checked'));
    var index = $checkbox.closest('.letter').data('index');
    letters[index].hidden = $checkbox.prop('checked');
    $editor.focus();
});

$editor.keydown(function(e) {
    var ch = e.which;
    if (ch == 8) { // Backspace
        return false;
    }
});

$editor.keyup(function(e) {
    var ch = e.which;
    if ((ch >= 65) && (ch <= 90) || (ch == 32)) {
        console.log('char: ' + ch);
        if (letters.length < EDITOR_MAX) {
            letters.push({
                char: String.fromCharCode(ch),
                hidden: false
            });
            caretPos++;
    ...