Improve example with autocomplete buttons

This commit is contained in:
2023-06-28 22:30:11 +02:00
parent 935fd47980
commit dd433ee4b4
5 changed files with 75 additions and 63 deletions

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import PriceChecker from "./components/PriceChecker.vue"; import PriceChecker from "./components/PriceChecker.vue";
</script> </script>
@@ -12,7 +11,6 @@ import PriceChecker from "./components/PriceChecker.vue";
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" /> <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a> </a>
</div> </div>
<HelloWorld msg="Vite + Vue" />
<PriceChecker /> <PriceChecker />
</template> </template>

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import { ref } from "vue";
defineProps<{ msg: string }>();
const count = ref(0);
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@@ -7,9 +7,12 @@ const props = withDefaults(
modelValue: string; modelValue: string;
options: string[]; options: string[];
alwaysShowSuggestions?: boolean; alwaysShowSuggestions?: boolean;
disabled?: boolean;
name?: string;
}>(), }>(),
{ {
alwaysShowSuggestions: false, alwaysShowSuggestions: false,
disabled: false,
} }
); );
@@ -23,11 +26,34 @@ const onModelValueChange = (value: string) => {
}; };
const hasFocus = ref(false); const hasFocus = ref(false);
const showSuggestions = ref(false);
let timeOutId = 0;
const onFocus = () => {
hasFocus.value = true;
showSuggestions.value = true;
clearTimeout(timeOutId);
};
const onBlur = () => {
hasFocus.value = false;
// Hide the suggestions later to give the click handlers time to react
timeOutId = setTimeout(() => {
showSuggestions.value = false;
}, 100);
};
const onButtonClick = (value: string) => {
if (value !== props.modelValue) {
emits("update:modelValue", value);
}
};
const hasMatch = computed<boolean>(() => const hasMatch = computed<boolean>(() =>
props.options.includes(props.modelValue) props.options.includes(props.modelValue)
); );
// If we would simply emit an auto complete event on update model value it would: // If we would simply emit an auto complete event on update model value it would:
// - not allow the parent to reject the new value without causing inconsistent state // - not allow the parent to reject the new value without causing inconsistent state
// - not handle the case where the parent is the one changing the value, forcing it to implement the autocomplete logic itself as well if needed // - not handle the case where the parent is the one changing the value, forcing it to implement the autocomplete logic itself as well if needed
@@ -42,16 +68,26 @@ watch(
</script> </script>
<template> <template>
<TextInput <div>
:model-value="props.modelValue" <div style="display: flex">
@update:model-value="onModelValueChange" <TextInput
@focus="hasFocus = true" :model-value="props.modelValue"
@blur="hasFocus = false" :disabled="props.disabled"
/> :name="props.name"
@update:model-value="onModelValueChange"
@focus="onFocus"
@blur="onBlur"
/>
<span style="margin-left: 5px" v-show="hasMatch"></span>
</div>
<ul v-if="hasFocus && (!hasMatch || props.alwaysShowSuggestions)"> <div
<li v-for="option in props.options"> style="display: flex; flex-direction: column; gap: 10px; margin-top: 5px"
{{ option }} v-if="showSuggestions && (!hasMatch || props.alwaysShowSuggestions)"
</li> >
</ul> <button v-for="option in props.options" @click="onButtonClick(option)">
{{ option }}
</button>
</div>
</div>
</template> </template>

View File

@@ -1,7 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ const props = withDefaults(
modelValue: string; defineProps<{
}>(); modelValue: string;
disabled?: boolean;
name?: string;
}>(),
{
disabled: false,
}
);
const emits = defineEmits<{ const emits = defineEmits<{
(e: "update:modelValue", value: string): void; (e: "update:modelValue", value: string): void;
@@ -13,6 +20,8 @@ const emits = defineEmits<{
<template> <template>
<input <input
:value="props.modelValue" :value="props.modelValue"
:disabled="props.disabled"
:name="props.name"
@input="emits('update:modelValue', (<HTMLInputElement>$event.target).value)" @input="emits('update:modelValue', (<HTMLInputElement>$event.target).value)"
@blur="emits('blur')" @blur="emits('blur')"
@focus="emits('focus')" @focus="emits('focus')"

View File

@@ -33,6 +33,11 @@ const onActivityCodeAutocomplete = () => {
operationCode.value = operationCode.value =
activityModel.value.find((e) => e.activityCode === activityCode.value) activityModel.value.find((e) => e.activityCode === activityCode.value)
?.operationCodes[0] ?? ""; ?.operationCodes[0] ?? "";
const operationCodeInput = document.getElementsByName("operationCodeInput");
if (operationCodeInput.length > 0) {
setTimeout(() => operationCodeInput[0].focus(), 1);
}
}; };
const onOperationCodeChange = (value: string) => { const onOperationCodeChange = (value: string) => {
@@ -47,33 +52,35 @@ const onOperationCodeAutocomplete = () => {
</script> </script>
<template> <template>
<div class="inputs"> <div class="input-group" style="margin-bottom: 10px">
<label style="width: 8em">Activiteitscode</label>
<AutoCompleteInput <AutoCompleteInput
:model-value="activityCode" :model-value="activityCode"
:options="activityCodeOptions" :options="activityCodeOptions"
@update:model-value="onActivityCodeChange" @update:model-value="onActivityCodeChange"
@autocomplete="onActivityCodeAutocomplete" @autocomplete="onActivityCodeAutocomplete"
/> />
</div>
<div class="input-group">
<label style="width: 8em">Bewerkingscode</label>
<AutoCompleteInput <AutoCompleteInput
:model-value="operationCode" :model-value="operationCode"
@update:model-value="onOperationCodeChange" @update:model-value="onOperationCodeChange"
@autocomplete="onOperationCodeAutocomplete" @autocomplete="onOperationCodeAutocomplete"
:options="operationCodeOptions" :options="operationCodeOptions"
:disabled="operationCodeOptions.length < 2"
name="operationCodeInput"
always-show-suggestions always-show-suggestions
/> />
</div> </div>
<p v-if="price">{{ price }} euro</p> <p v-if="price">Berekende prijs: {{ price }} euro</p>
</template> </template>
<style> <style>
.inputs { .input-group {
display: flex; display: flex;
flex-direction: column; gap: 10px;
}
.inputs > * {
margin-bottom: 1em;
} }
</style> </style>