Improve example with autocomplete buttons
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
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>
|
||||||
|
|||||||
@@ -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')"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user