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>
<script src="http://lorenzoongithub.github.io/completely/complete.ly.1.0.1.js"></script>
<i-expression-editor></i-expression-editor>
<script type="text/html" id="i-compound">
<span class="i-compound i-term i-box" data-bind="foreach: children, css: { 'selected': selected }"><span data-bind="template: { name: $data.template, data: $data }"></span></span>
</script>
<script type="text/html" id="i-atom">
<span class="i-atom i-term" data-bind="css: { 'selected': selected }"><span class="i-box i-text-box" data-bind="text: value, visible: !editing(), click: cellClicked"></span><span class="i-box" data-bind="visible: editing, cellEditor: { value: value, hasfocus: editing, autosizeInput: { 'space' : 0 } }"></span></span></script>
CSS
i-expression-editor {
outline: none;
font-size: 13px;
}
i-expression-editor .i-atom {
font-family: monospace;
}
i-expression-editor .i-box {
padding: 2px;
border-radius: 3px;
display: inline-block;
border: 1px solid #dadada;
/* -3px to cancel padding + border */
}
i-expression-editor .i-atom.selected .i-text-box {
cursor: text;
}
i-expression-editor .i-atom .i-text-box {
cursor: pointer;
}
i-expression-editor .i-atom .i-text-box:hover {
background-color: #eee;
}
i-expression-editor input {
font-size: 13px;
font-family: monospace;
background: transparent;
display: inline;
outline: none;
}
i-expression-editor .selected > .i-box,
i-expression-editor .selected.i-box {
border: 1px solid black;
}
JavaScript
var shiftKey;
$(document).on('keyup keydown', function (e) {
shiftKey = e.shiftKey;
});
ko.components.register('i-expression-editor', {
viewModel: {
createViewModel: function (params, componentInfo) {
function Compound(term) {
this.children = ko.observableArray(term.map(createModel));
this.template = 'i-compound';
this.selected = ko.pureComputed(function () {
return model.multiSelect().indexOf(this) !== -1;
}, this);
this.empty = function () {
return this.children().length === 0;
};
}
function Atom(value) {
this.value = ko.observable(value);
this.template = 'i-atom';
this.selected = ko.pureComputed(function () {
return model.multiSelect().indexOf(this) !== -1;
}, this);
this.editing = ko.pureComputed({
read: function () {
return this.selected() && model.editMode();
},
owner: this
});
this.empty = function () {
return this.value() === '';
};
this.cellClicked = function () {
if (this.selected()) {
model.editMode(true);
} else {
model.editMode(false);
model.select(this);
}
};
this.onEnter = function () {
model.editMode(false);
};
this.onEscape = this.onEnter;
this.onTab = function () {
if (shiftKey) {
model.moveSelectionLeft();
} else {
...