api-client/components/smart/AutoComplete.vue

238 lines
5.1 KiB
Vue
Raw Normal View History

<template>
2019-09-04 02:48:24 +00:00
<div class="autocomplete-wrapper">
2019-10-05 09:27:24 +00:00
<input
2021-05-18 09:27:29 +00:00
ref="acInput"
v-model="text"
2021-07-10 18:08:35 +00:00
class="
bg-primaryLight
2021-07-17 17:40:28 +00:00
flex
2021-07-23 19:12:31 +00:00
font-semibold font-mono
2021-07-17 17:40:28 +00:00
flex-1
2021-07-23 19:12:31 +00:00
py-2
2021-07-17 17:40:28 +00:00
px-4
2021-07-10 18:08:35 +00:00
focus:outline-none
"
2019-10-05 09:27:24 +00:00
type="text"
:placeholder="placeholder"
:spellcheck="spellcheck"
:autocapitalize="autocapitalize"
2019-10-05 09:27:24 +00:00
:autocorrect="spellcheck"
2020-12-11 16:54:34 +00:00
:class="styles"
2021-05-18 09:27:29 +00:00
@input="updateSuggestions"
@keyup="updateSuggestions"
2021-07-05 12:56:00 +00:00
@click="updateSuggestions"
2021-05-18 09:27:29 +00:00
@keydown="handleKeystroke"
2021-07-13 05:37:29 +00:00
@change="$emit('change', $event)"
2019-10-05 09:27:24 +00:00
/>
<ul
v-if="suggestions.length > 0 && suggestionsVisible"
2021-05-18 09:27:29 +00:00
class="suggestions"
2019-10-05 09:27:24 +00:00
:style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }"
>
<li
v-for="(suggestion, index) in suggestions"
2021-07-13 23:49:08 +00:00
:key="`suggestion-${index}`"
2021-05-18 09:27:29 +00:00
:class="{ active: currentSuggestionIndex === index }"
2021-07-05 12:56:00 +00:00
@click.prevent="forceSuggestion(suggestion)"
2019-11-12 04:52:50 +00:00
>
{{ suggestion }}
</li>
2019-10-05 09:27:24 +00:00
</ul>
2019-09-04 02:48:24 +00:00
</div>
</template>
<script>
2019-11-02 05:32:21 +00:00
export default {
props: {
spellcheck: {
type: Boolean,
default: true,
2020-02-24 18:44:50 +00:00
required: false,
},
autocapitalize: {
type: String,
default: "off",
required: false,
2019-09-04 02:48:24 +00:00
},
2019-11-02 05:32:21 +00:00
placeholder: {
type: String,
default: "",
2020-02-24 18:44:50 +00:00
required: false,
2019-09-04 02:48:24 +00:00
},
2019-11-02 05:32:21 +00:00
source: {
type: Array,
2020-02-24 18:44:50 +00:00
required: true,
},
value: {
type: String,
default: "",
2020-02-24 18:44:50 +00:00
required: false,
},
2020-12-11 16:54:34 +00:00
styles: {
type: String,
default: "",
},
2019-11-02 05:32:21 +00:00
},
2019-11-02 05:32:21 +00:00
data() {
return {
text: this.value,
2019-11-02 05:32:21 +00:00
selectionStart: 0,
suggestionsOffsetLeft: 0,
currentSuggestionIndex: -1,
2020-02-24 18:44:50 +00:00
suggestionsVisible: false,
}
2019-11-02 05:32:21 +00:00
},
2021-05-18 09:27:29 +00:00
computed: {
/**
* Gets the suggestions list to be displayed under the input box.
*
* @returns {default.props.source|{type, required}}
*/
suggestions() {
const input = this.text.substring(0, this.selectionStart)
return (
this.source
.filter(
(entry) =>
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 6 suggestions.
.slice(0, 6)
)
},
},
watch: {
text() {
this.$emit("input", this.text)
},
value(newValue) {
this.text = newValue
2021-06-06 11:05:30 +00:00
},
2021-05-18 09:27:29 +00:00
},
mounted() {
this.updateSuggestions({
target: this.$refs.acInput,
})
},
2019-11-02 05:32:21 +00:00
methods: {
updateSuggestions(event) {
// Hide suggestions if ESC pressed.
if (event.code && event.code === "Escape") {
2020-02-24 18:44:50 +00:00
event.preventDefault()
this.suggestionsVisible = false
this.currentSuggestionIndex = -1
return
2019-09-04 02:48:24 +00:00
}
2019-11-02 05:32:21 +00:00
// As suggestions is a reactive property, this implicitly
// causes suggestions to update.
2020-02-24 18:44:50 +00:00
this.selectionStart = this.$refs.acInput.selectionStart
this.suggestionsOffsetLeft = 12 * this.selectionStart
this.suggestionsVisible = true
2019-09-04 02:48:24 +00:00
},
2019-11-02 05:32:21 +00:00
forceSuggestion(text) {
2021-05-18 09:27:29 +00:00
const input = this.text.substring(0, this.selectionStart)
2020-02-24 18:44:50 +00:00
this.text = input + text
2019-11-02 05:32:21 +00:00
2020-02-24 18:44:50 +00:00
this.selectionStart = this.text.length
this.suggestionsVisible = true
this.currentSuggestionIndex = -1
},
2019-09-04 02:48:24 +00:00
2019-11-02 05:32:21 +00:00
handleKeystroke(event) {
switch (event.code) {
case "ArrowUp":
2020-02-24 18:44:50 +00:00
event.preventDefault()
2019-11-02 05:32:21 +00:00
this.currentSuggestionIndex =
2021-05-18 09:27:29 +00:00
this.currentSuggestionIndex - 1 >= 0
? this.currentSuggestionIndex - 1
: 0
2020-02-24 18:44:50 +00:00
break
2019-11-02 05:32:21 +00:00
case "ArrowDown":
2020-02-24 18:44:50 +00:00
event.preventDefault()
2019-11-02 05:32:21 +00:00
this.currentSuggestionIndex =
this.currentSuggestionIndex < this.suggestions.length - 1
? this.currentSuggestionIndex + 1
2020-02-24 18:44:50 +00:00
: this.suggestions.length - 1
break
2019-11-02 05:32:21 +00:00
2021-05-18 09:27:29 +00:00
case "Tab": {
const activeSuggestion =
this.suggestions[
this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0
]
if (!activeSuggestion) {
return
}
event.preventDefault()
2021-05-18 09:27:29 +00:00
const input = this.text.substring(0, this.selectionStart)
this.text = input + activeSuggestion
2020-02-24 18:44:50 +00:00
break
2021-05-18 09:27:29 +00:00
}
2019-11-02 05:32:21 +00:00
}
2020-02-24 18:44:50 +00:00
},
2019-11-02 05:32:21 +00:00
},
2021-05-18 09:27:29 +00:00
}
</script>
2019-11-02 05:32:21 +00:00
2021-05-18 09:27:29 +00:00
<style scoped lang="scss">
.autocomplete-wrapper {
@apply relative;
2021-07-20 10:29:30 +00:00
@apply flex flex-1;
2019-11-02 05:32:21 +00:00
2021-05-18 09:27:29 +00:00
input:focus + ul.suggestions,
ul.suggestions:hover {
@apply block;
}
2019-11-02 05:32:21 +00:00
2021-05-18 09:27:29 +00:00
ul.suggestions {
@apply hidden;
2021-06-12 16:46:17 +00:00
@apply bg-primary;
2021-05-18 09:27:29 +00:00
@apply absolute;
@apply mx-2;
@apply left-0;
@apply z-50;
@apply shadow-lg;
top: calc(100% - 4px);
2021-05-18 09:27:29 +00:00
border-radius: 0 0 8px 8px;
li {
@apply w-full;
@apply block;
2021-06-26 10:41:19 +00:00
@apply py-2 px-4;
@apply font-semibold font-mono;
2021-05-18 09:27:29 +00:00
&:last-child {
border-radius: 0 0 8px 8px;
}
&:hover,
&.active {
2021-06-12 16:46:17 +00:00
@apply bg-accent;
@apply text-primary;
2021-05-18 09:27:29 +00:00
@apply cursor-pointer;
}
}
}
2020-02-24 18:44:50 +00:00
}
2021-05-18 09:27:29 +00:00
</style>