Merge pull request #123 from NBTX/master

⌨️Add autocomplete widget with command line completion
This commit is contained in:
Liyas Thomas 2019-08-31 22:18:07 +05:30 committed by GitHub
commit 7c49f58565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 810 additions and 583 deletions

View File

@ -10,6 +10,8 @@
--bg-dark-color: #000000;
// Background color
--bg-color: #121212;
// Auto-complete color
--atc-color: #212121;
// Text color
--fg-color: #FFF;
@ -26,6 +28,8 @@
--bg-dark-color: #ffffff;
// Background color
--bg-color: #F6F8FA;
// Auto-complete color
--atc-color: #F1F1F1;
// Text color
--fg-color: #121212;
@ -36,4 +40,4 @@
--ac-color: #51FF0D;
// Active text color
--act-color: #121212;
}
}

190
components/autocomplete.vue Normal file
View File

@ -0,0 +1,190 @@
<template>
<div class="autocomplete-wrapper">
<label>
<slot />
<input type="text"
:placeholder="placeholder"
v-model="value"
@input="updateSuggestions"
@keyup="updateSuggestions"
@click="updateSuggestions"
@keydown="handleKeystroke"
ref="acInput"
:spellcheck="spellcheck"
:autocapitalize="spellcheck"
:autocorrect="spellcheck">
<ul class="suggestions" v-if="suggestions.length > 0 && suggestionsVisible" :style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }">
<li v-for="(suggestion, index) in suggestions" @click.prevent="forceSuggestion(suggestion)" :class="{ active: currentSuggestionIndex === index }">{{ suggestion }}</li>
</ul>
</label>
</div>
</template>
<style lang="scss" scoped>
.autocomplete-wrapper {
position: relative;
input:focus + ul.suggestions, ul.suggestions:hover {
display: block;
}
ul.suggestions {
display: none;
background-color: var(--atc-color);
position: absolute;
top: 90%;
margin: 0 4px;
left: 0;
padding: 0;
border-radius: 0 0 4px 4px;
z-index: 9999;
transition: transform 200ms ease-out;
li {
width: 100%;
display: block;
margin: 5px 0;
padding: 10px 10px;
font-weight: 700;
font-size: 18px;
font-family: monospace;
white-space: pre-wrap;
&:hover, &.active {
background-color: var(--ac-color);
}
}
}
}
</style>
<script>
const KEY_TAB = 9;
const KEY_ESC = 27;
const KEY_ARROW_UP = 38;
const KEY_ARROW_DOWN = 40;
export default {
props: {
spellcheck: {
type: Boolean,
default: true,
required: false
},
placeholder: {
type: String,
default: 'Start typing...',
required: false
},
source: {
type: Array,
required: true
},
value: {}
},
watch: {
value () {
this.$emit('input', this.value);
}
},
data () {
return {
value: "",
selectionStart: 0,
suggestionsOffsetLeft: 0,
currentSuggestionIndex: -1,
suggestionsVisible: false
}
},
methods: {
updateSuggestions (event) {
// Hide suggestions if ESC pressed.
if(event.which && event.which === KEY_ESC){
event.preventDefault();
this.suggestionsVisible = false;
this.currentSuggestionIndex = -1;
return;
}
// As suggestions is a reactive property, this implicitly
// causes suggestions to update.
this.selectionStart = this.$refs.acInput.selectionStart;
this.suggestionsOffsetLeft = (12 * this.selectionStart);
this.suggestionsVisible = true;
},
forceSuggestion (text) {
let input = this.value.substring(0, this.selectionStart);
this.value = input + text;
this.selectionStart = this.value.length;
this.suggestionsVisible = true;
this.currentSuggestionIndex = -1;
},
handleKeystroke (event) {
if(event.which === KEY_ARROW_UP){
event.preventDefault();
this.currentSuggestionIndex = this.currentSuggestionIndex - 1 >= 0
? this.currentSuggestionIndex - 1
: 0;
}else if(event.which === KEY_ARROW_DOWN){
event.preventDefault();
this.currentSuggestionIndex = this.currentSuggestionIndex < this.suggestions.length - 1
? this.currentSuggestionIndex + 1
: this.suggestions.length - 1;
}
if(event.which === KEY_TAB){
event.preventDefault();
let activeSuggestion = this.suggestions[this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0];
if(activeSuggestion){
let input = this.value.substring(0, this.selectionStart);
this.value = input + activeSuggestion;
}
}
}
},
computed: {
/**
* Gets the suggestions list to be displayed under the input box.
*
* @returns {default.props.source|{type, required}}
*/
suggestions () {
let input = this.value.substring(0, this.selectionStart);
return this.source.filter((entry) => {
return entry.toLowerCase().startsWith(input.toLowerCase())
&& input.toLowerCase() !== entry.toLowerCase();
})
// Cut off the part that's already been typed.
.map((entry) => entry.substring(this.selectionStart))
// We only want the top 3 suggestions.
.slice(0, 3);
}
},
mounted () {
this.updateSuggestions({
target: this.$refs.acInput
});
}
}
</script>

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "postwoman",
"version": "1.0.0",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -10128,6 +10128,11 @@
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
"integrity": "sha512-jcXl1cYDxGZX+aF9vsUauXWnUkXm8oQxnvLTJ8UMTmMxwzbmlHX7vs0xGDdEej91vJpBNrdNNseWPxboTvI+UA=="
},
"vuejs-auto-complete": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/vuejs-auto-complete/-/vuejs-auto-complete-0.9.0.tgz",
"integrity": "sha512-7UV3s9bXdnsbGARhHcOuDAszGUsz7JpsFKBfHQuQvo4rfH0yQIru2Rb/x2bWU+m+VW4fS9DKDSYi6tY511QSIA=="
},
"vuex": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",

View File

@ -5,14 +5,12 @@
"author": "liyasthomas",
"private": true,
"scripts": {
"predev": "node build.js --dev",
"predev": "node build.js --dev",
"dev": "nuxt",
"prebuild": "node build.js",
"prebuild": "node build.js",
"build": "nuxt build",
"start": "nuxt start",
"pregenerate": "node build.js",
"pregenerate": "node build.js",
"generate": "nuxt generate"
},
"dependencies": {
@ -20,6 +18,7 @@
"@nuxtjs/pwa": "^3.0.0-0",
"nuxt": "^2.9.2",
"vue-virtual-scroll-list": "^1.4.2",
"vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.1.0"
},
"devDependencies": {

File diff suppressed because it is too large Load Diff