⌨️Add autocomplete widget with command line completion
This commit is contained in:
@@ -10,6 +10,8 @@
|
|||||||
--bg-dark-color: #000000;
|
--bg-dark-color: #000000;
|
||||||
// Background color
|
// Background color
|
||||||
--bg-color: #121212;
|
--bg-color: #121212;
|
||||||
|
// Auto-complete color
|
||||||
|
--atc-color: #212121;
|
||||||
// Text color
|
// Text color
|
||||||
--fg-color: #FFF;
|
--fg-color: #FFF;
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
--bg-dark-color: #ffffff;
|
--bg-dark-color: #ffffff;
|
||||||
// Background color
|
// Background color
|
||||||
--bg-color: #F6F8FA;
|
--bg-color: #F6F8FA;
|
||||||
|
// Auto-complete color
|
||||||
|
--atc-color: #F1F1F1;
|
||||||
// Text color
|
// Text color
|
||||||
--fg-color: #121212;
|
--fg-color: #121212;
|
||||||
|
|
||||||
@@ -36,4 +40,4 @@
|
|||||||
--ac-color: #51FF0D;
|
--ac-color: #51FF0D;
|
||||||
// Active text color
|
// Active text color
|
||||||
--act-color: #121212;
|
--act-color: #121212;
|
||||||
}
|
}
|
||||||
|
|||||||
190
components/autocomplete.vue
Normal file
190
components/autocomplete.vue
Normal 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>
|
||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -10121,6 +10121,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
|
||||||
"integrity": "sha512-jcXl1cYDxGZX+aF9vsUauXWnUkXm8oQxnvLTJ8UMTmMxwzbmlHX7vs0xGDdEej91vJpBNrdNNseWPxboTvI+UA=="
|
"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": {
|
"vuex": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
"author": "liyasthomas",
|
"author": "liyasthomas",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predev": "node build.js --dev",
|
"predev": "node build.js --dev",
|
||||||
"dev": "nuxt",
|
"dev": "nuxt",
|
||||||
|
"prebuild": "node build.js",
|
||||||
"prebuild": "node build.js",
|
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"start": "nuxt start",
|
"start": "nuxt start",
|
||||||
|
"pregenerate": "node build.js",
|
||||||
"pregenerate": "node build.js",
|
|
||||||
"generate": "nuxt generate"
|
"generate": "nuxt generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -20,6 +18,7 @@
|
|||||||
"@nuxtjs/pwa": "^3.0.0-0",
|
"@nuxtjs/pwa": "^3.0.0-0",
|
||||||
"nuxt": "^2.0.0",
|
"nuxt": "^2.0.0",
|
||||||
"vue-virtual-scroll-list": "^1.4.2",
|
"vue-virtual-scroll-list": "^1.4.2",
|
||||||
|
"vuejs-auto-complete": "^0.9.0",
|
||||||
"vuex-persist": "^2.0.1"
|
"vuex-persist": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
1121
pages/index.vue
1121
pages/index.vue
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user