Improve example with autocomplete buttons
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import HelloWorld from "./components/HelloWorld.vue";
|
||||
import PriceChecker from "./components/PriceChecker.vue";
|
||||
</script>
|
||||
|
||||
@@ -12,7 +11,6 @@ import PriceChecker from "./components/PriceChecker.vue";
|
||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" />
|
||||
|
||||
<PriceChecker />
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
@@ -7,9 +7,12 @@ const props = withDefaults(
|
||||
modelValue: string;
|
||||
options: string[];
|
||||
alwaysShowSuggestions?: boolean;
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
}>(),
|
||||
{
|
||||
alwaysShowSuggestions: false,
|
||||
disabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,11 +26,34 @@ const onModelValueChange = (value: string) => {
|
||||
};
|
||||
|
||||
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>(() =>
|
||||
props.options.includes(props.modelValue)
|
||||
);
|
||||
|
||||
// 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 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>
|
||||
|
||||
<template>
|
||||
<TextInput
|
||||
:model-value="props.modelValue"
|
||||
@update:model-value="onModelValueChange"
|
||||
@focus="hasFocus = true"
|
||||
@blur="hasFocus = false"
|
||||
/>
|
||||
<div>
|
||||
<div style="display: flex">
|
||||
<TextInput
|
||||
:model-value="props.modelValue"
|
||||
: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)">
|
||||
<li v-for="option in props.options">
|
||||
{{ option }}
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 10px; margin-top: 5px"
|
||||
v-if="showSuggestions && (!hasMatch || props.alwaysShowSuggestions)"
|
||||
>
|
||||
<button v-for="option in props.options" @click="onButtonClick(option)">
|
||||
{{ option }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: string;
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: "update:modelValue", value: string): void;
|
||||
@@ -13,6 +20,8 @@ const emits = defineEmits<{
|
||||
<template>
|
||||
<input
|
||||
:value="props.modelValue"
|
||||
:disabled="props.disabled"
|
||||
:name="props.name"
|
||||
@input="emits('update:modelValue', (<HTMLInputElement>$event.target).value)"
|
||||
@blur="emits('blur')"
|
||||
@focus="emits('focus')"
|
||||
|
||||
@@ -33,6 +33,11 @@ const onActivityCodeAutocomplete = () => {
|
||||
operationCode.value =
|
||||
activityModel.value.find((e) => e.activityCode === activityCode.value)
|
||||
?.operationCodes[0] ?? "";
|
||||
|
||||
const operationCodeInput = document.getElementsByName("operationCodeInput");
|
||||
if (operationCodeInput.length > 0) {
|
||||
setTimeout(() => operationCodeInput[0].focus(), 1);
|
||||
}
|
||||
};
|
||||
|
||||
const onOperationCodeChange = (value: string) => {
|
||||
@@ -47,33 +52,35 @@ const onOperationCodeAutocomplete = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inputs">
|
||||
<div class="input-group" style="margin-bottom: 10px">
|
||||
<label style="width: 8em">Activiteitscode</label>
|
||||
<AutoCompleteInput
|
||||
:model-value="activityCode"
|
||||
:options="activityCodeOptions"
|
||||
@update:model-value="onActivityCodeChange"
|
||||
@autocomplete="onActivityCodeAutocomplete"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label style="width: 8em">Bewerkingscode</label>
|
||||
<AutoCompleteInput
|
||||
:model-value="operationCode"
|
||||
@update:model-value="onOperationCodeChange"
|
||||
@autocomplete="onOperationCodeAutocomplete"
|
||||
:options="operationCodeOptions"
|
||||
:disabled="operationCodeOptions.length < 2"
|
||||
name="operationCodeInput"
|
||||
always-show-suggestions
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="price">{{ price }} euro</p>
|
||||
<p v-if="price">Berekende prijs: {{ price }} euro</p>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.inputs {
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inputs > * {
|
||||
margin-bottom: 1em;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user