JSFiddle - React, Tailwind, and code Playground
by PhilQ
HTML
<script src="//code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script>
<script src="//stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<br />
<div id="app" class="container">
<div class="row">
<div class="col">
<div class="form-control" ref="input"
spellcheck="false"
:contenteditable="editable"
:class="{'has-tags':selected_tags.length}"
@mousedown="inputMouseDown"
@keydown.shift.tab.prevent="inputKeyShiftTab"
@keydown.left="inputKeyLeft($event)"
@keydown.up.prevent="inputKeyUp"
@keydown.down.prevent="inputKeyDown"
>
<template v-for="(tag,i) in selected_tags">
<a href="#"
class="tag btn btn-sm btn-light"
@focus="tagFocus(i)"
@blur="tagBlur(i,$event)"
@click.prevent.stop="tagClick(i)"
@mousedown.prevent.stop="tagMouseDown(i)"
@keydown.tab.stop="tagKeyTab(i,$event)"
>{{tag.name}}</a>
</template> {{input_value}}
</div>
</div>
</div>
</div>
CSS
.form-control {
min-height: calc(2.19rem + 2px);
height: auto;
cursor: text;
padding-left: 0;
padding-right: 0;
}
.form-control.focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
box-shadow: 0 0 0 0.2rem rgba(255,123,0,.25);
}
.has-tag {
padding-left: 0.5rem;
/* padding-right: 0.5rem; */
}
.tag {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.tag + .tag {
margin-left: 0;
}
.tag:focus {
background: #3490dc;
color: #fff;
}
JavaScript
var app = new Vue({
el: '#app',
data: {
editable: true,
selected_tags: [
{name: 'Tag 1', id: 1},
{name: 'Tag 2', id: 2},
{name: 'Tag 3', id: 3},
],
input_value: 'Text en zo',
},
computed: {
},
methods: {
tagFocus(idx) {
console.log('tag-focus', idx);
},
tagBlur(idx,evt) {
console.log('tag-blur', idx);
var to_el = evt.relatedTarget || evt.toElement;
if ( ! to_el || to_el && !$(to_el).hasClass('tag') ) {
this.editable = true;
}
},
tagMouseDown(idx,evt) {
console.log('tag-mousedown', idx);
this.editable = false;
},
tagClick(idx,evt) {
console.log('tag-click', idx);
this.$refs.input.childNodes[idx].focus();
},
tagKeyTab(idx,evt) {
if (!evt.shiftKey && idx == this.selected_tags.length-1) {
evt.preventDefault();
this.editable = true;
Vue.nextTick(() => {
this.$refs.input.focus();
this.placeCaret(this.$refs.input, true, false);
});
}
},
inputMouseDown() {
console.log('input-mousedown');
this.editable = true;
Vue.nextTick(() => { this.$refs.input.focus(); });
},
inputClick() {
//this.$refs.input.focus();
},
inputKeyShiftTab() {
this.editable = false;
Vue.nextTick(() => { this.$refs.input.childNodes[this.$refs.input.childNodes.length-2].focus(); });
},
inputKeyLeft(evt) {
var selectedRange = window.getSelection().getRangeAt(0);
console.log('range:', selectedRange.startOffset, selectedRange.endOffset);
if (1 == selectedRange.startOffset) {
evt.preventDefault();
this.editable = false;
Vue.nextTick(() => { this.$refs.input.childNodes[this.$refs.input.childNodes.length-2].focus(); });
}
},
inputKeyUp() {}, // reserve for autocomplete
inputKeyDown() {}, // reserve for autocomplete
placeCaret(el, collapse, atStart) {
// feature test to see which methods to use for given browser
if (
typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined"
) {
//...