JSFiddle - React, Tailwind, and code Playground

by waxwing

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-debug.js"></script>
<script src="http://www.fakeyourbeauty.com/jquery.autosize.input.debug.js"></script>
<i-editor></i-editor>

CSS

i-editor {
    outline: none;
}

i-editor .i-term {
    font-family: monospace;
    padding: 2px;
    border-radius: 3px;
    font-size: 14px;
    border: 1px solid transparent;
}
i-editor span input {
    font-family: monospace;
    font-size: 14px;
    border: none;
    background: transparent;
    display: inline;
    outline: none;
}
i-editor .selected {
    border: 1px solid black;
}

JavaScript

ko.components.register('i-editor', {
    viewModel: {
        createViewModel: function (params, componentInfo) {

            function Compound(term) {
                this.children = ko.observableArray(term.map(createTerm));
                this.template = 'i-compound';
            }

            function Atom(value) {
                this.value = ko.observable(value);
                this.template = 'i-atom';
            }

            function createTerm(expr) {
                if (Array.isArray(expr)) {
                    return new Compound(expr);
                } else {
                    return new Atom(expr);
                }
            }

            var model = new function () {
                var self = this;
                this.root = createTerm(['value', '>', ['a', '+', 10]], this);
                this.editMode = ko.observable(false);
                this.selected = ko.observable();
                this.isSelected = function (term) {
                    return self.selected() === term;
                };
                this.isEditing = function (term) {
                    return self.editMode() && self.isSelected(term);
                };
            };

            var element = componentInfo.element;
            element.tabIndex = 0;
            
            function walkPostOrder(term, callback) {
                if (term.children) {
                    return ko.unwrap(term.children).every(function (it) {
                        return walkPostOrder(it, callback);
                    });
                }
                return callback(term);
            }
            
            ko.utils.registerEventHandler(element, 'keydown', function (event) {
                if (event.keyCode === 39) {
                    var selectNext = false;
                    walkPostOrder(model.root, function (term) {
                        if (selectNext) {
                            console.log('selecting');
                           ...