fix: don't cut off the part that's already been typed (#3054)

This commit is contained in:
Liyas Thomas
2023-05-24 02:06:02 +05:30
committed by GitHub
parent aeb9172144
commit 1fe0b8861d

View File

@@ -1,13 +1,27 @@
<template> <template>
<div class="autocomplete-wrapper"> <div class="autocomplete-wrapper">
<input ref="acInput" v-model="text" type="text" autocomplete="off" :placeholder="placeholder" :spellcheck="spellcheck" <input
:autocapitalize="autocapitalize" :class="styles" @input.stop="onInput" @keyup="updateSuggestions" ref="acInput"
@click="updateSuggestions" @keydown="handleKeystroke" @change="emit('change', $event)" /> v-model="text"
type="text"
<ul v-if="suggestions.length > 0 && suggestionsVisible" class="suggestions" autocomplete="off"
:style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }"> :placeholder="placeholder"
<li v-for="(suggestion, index) in suggestions" :key="`suggestion-${index}`" :spellcheck="spellcheck"
:class="{ active: currentSuggestionIndex === index }" @click.prevent="forceSuggestion(suggestion)"> :autocapitalize="autocapitalize"
:class="styles"
@input.stop="onInput"
@keyup="updateSuggestions"
@click="updateSuggestions"
@keydown="handleKeystroke"
@change="emit('change', $event)"
/>
<ul v-if="suggestions.length > 0 && suggestionsVisible" class="suggestions">
<li
v-for="(suggestion, index) in suggestions"
:key="`suggestion-${index}`"
:class="{ active: currentSuggestionIndex === index }"
@click.prevent="forceSuggestion(suggestion)"
>
{{ suggestion }} {{ suggestion }}
</li> </li>
</ul> </ul>
@@ -62,11 +76,9 @@ const emit = defineEmits<{
const text = ref(props.value) const text = ref(props.value)
const selectionStart = ref(0) const selectionStart = ref(0)
const suggestionsOffsetLeft = ref(0)
const currentSuggestionIndex = ref(-1) const currentSuggestionIndex = ref(-1)
const suggestionsVisible = ref(false) const suggestionsVisible = ref(false)
onMounted(() => { onMounted(() => {
updateSuggestions({ updateSuggestions({
target: acInput, target: acInput,
@@ -82,14 +94,11 @@ const suggestions = computed(() => {
entry.toLowerCase().startsWith(input.toLowerCase()) && entry.toLowerCase().startsWith(input.toLowerCase()) &&
input.toLowerCase() !== entry.toLowerCase() input.toLowerCase() !== entry.toLowerCase()
) )
// Cut off the part that's already been typed.
.map((entry) => entry.substring(selectionStart.value))
// We only want the top 10 suggestions. // We only want the top 10 suggestions.
.slice(0, 10) .slice(0, 10)
) )
}) })
function updateSuggestions(event: any) { function updateSuggestions(event: any) {
// Hide suggestions if ESC pressed. // Hide suggestions if ESC pressed.
if (event.code && event.code === "Escape") { if (event.code && event.code === "Escape") {
@@ -102,18 +111,16 @@ function updateSuggestions(event: any) {
// As suggestions is a reactive property, this implicitly // As suggestions is a reactive property, this implicitly
// causes suggestions to update. // causes suggestions to update.
selectionStart.value = acInput.value?.selectionStart ?? -1 selectionStart.value = acInput.value?.selectionStart ?? -1
suggestionsOffsetLeft.value = 12 * selectionStart.value
suggestionsVisible.value = true suggestionsVisible.value = true
} }
const onInput = (e: Event) => { const onInput = (e: Event) => {
emit('input', (e.target as HTMLInputElement).value) emit("input", (e.target as HTMLInputElement).value)
updateSuggestions(e) updateSuggestions(e)
} }
function forceSuggestion(str: string) { function forceSuggestion(suggestion: string) {
const input = text.value.substring(0, selectionStart.value) text.value = suggestion
text.value = input + str
selectionStart.value = text.value.length selectionStart.value = text.value.length
suggestionsVisible.value = true suggestionsVisible.value = true
@@ -124,18 +131,9 @@ function forceSuggestion(str: string) {
function handleKeystroke(event: any) { function handleKeystroke(event: any) {
switch (event.code) { switch (event.code) {
case "Enter":
event.preventDefault()
if (currentSuggestionIndex.value > -1)
forceSuggestion(
suggestions.value.find(
(_item, index) => index === currentSuggestionIndex.value
)!
)
break
case "ArrowUp": case "ArrowUp":
event.preventDefault() event.preventDefault()
currentSuggestionIndex.value = currentSuggestionIndex.value =
currentSuggestionIndex.value - 1 >= 0 currentSuggestionIndex.value - 1 >= 0
? currentSuggestionIndex.value - 1 ? currentSuggestionIndex.value - 1
@@ -144,32 +142,41 @@ function handleKeystroke(event: any) {
case "ArrowDown": case "ArrowDown":
event.preventDefault() event.preventDefault()
currentSuggestionIndex.value = currentSuggestionIndex.value =
currentSuggestionIndex.value < suggestions.value.length - 1 currentSuggestionIndex.value < suggestions.value.length - 1
? currentSuggestionIndex.value + 1 ? currentSuggestionIndex.value + 1
: suggestions.value.length - 1 : suggestions.value.length - 1
break break
case "Enter":
event.preventDefault()
if (currentSuggestionIndex.value > -1)
forceSuggestion(
suggestions.value.find(
(_item, index) => index === currentSuggestionIndex.value
)!
)
break
case "Tab": { case "Tab": {
event.preventDefault()
const activeSuggestion = const activeSuggestion =
suggestions.value[ suggestions.value[
currentSuggestionIndex.value >= 0 ? currentSuggestionIndex.value : 0 currentSuggestionIndex.value >= 0 ? currentSuggestionIndex.value : 0
] ]
if (!activeSuggestion) { if (!activeSuggestion) return
return
}
event.preventDefault() forceSuggestion(activeSuggestion)
const input = text.value.substring(0, selectionStart.value)
text.value = input + activeSuggestion
break break
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.autocomplete-wrapper { .autocomplete-wrapper {
@apply relative; @apply relative;
@@ -181,16 +188,17 @@ function handleKeystroke(event: any) {
} }
ul.suggestions { ul.suggestions {
@apply absolute;
@apply hidden; @apply hidden;
@apply bg-popover; @apply bg-popover;
@apply absolute; @apply -left-px;
@apply mx-2;
@apply left-0;
@apply z-50; @apply z-50;
@apply shadow-lg; @apply shadow-lg;
@apply max-h-46; @apply max-h-46;
@apply overflow-y-auto; @apply overflow-y-auto;
top: calc(100% - 4px); @apply border-b border-x border-divider;
top: calc(100% + 1px);
border-radius: 0 0 8px 8px; border-radius: 0 0 8px 8px;
li { li {
@@ -198,15 +206,16 @@ function handleKeystroke(event: any) {
@apply block; @apply block;
@apply py-2 px-4; @apply py-2 px-4;
@apply text-secondary; @apply text-secondary;
@apply font-semibold;
&:last-child { &:last-child {
border-radius: 0 0 8px 8px; border-radius: 0 0 0 8px;
} }
&:hover, &:hover,
&.active { &.active {
@apply bg-accentDark; @apply bg-primaryDark;
@apply text-accentContrast; @apply text-secondaryDark;
@apply cursor-pointer; @apply cursor-pointer;
} }
} }