feat(SelectMenu): allow control of search query

Resolves #1174
This commit is contained in:
Benjamin Canac
2024-01-03 15:11:20 +01:00
parent e8f573b6bb
commit f735db04d6
3 changed files with 52 additions and 13 deletions

View File

@@ -0,0 +1,16 @@
<script setup>
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref()
const query = ref('Wade')
</script>
<template>
<USelectMenu
v-model="selected"
v-model:query="query"
:options="people"
placeholder="Select a person"
searchable
/>
</template>

View File

@@ -146,6 +146,18 @@ componentProps:
--- ---
:: ::
### Control the query :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
Use a `v-model:query` to control the search query.
::component-example
---
component: 'select-menu-example-search-query'
componentProps:
class: 'w-full lg:w-48'
---
::
## Creatable ## Creatable
Use the `creatable` prop to enable the creation of new options when the search doesn't return any results (only works with `searchable`). Use the `creatable` prop to enable the creation of new options when the search doesn't return any results (only works with `searchable`).

View File

@@ -58,14 +58,13 @@
<component :is="searchable ? 'HComboboxOptions' : 'HListboxOptions'" static :class="[uiMenu.base, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]"> <component :is="searchable ? 'HComboboxOptions' : 'HListboxOptions'" static :class="[uiMenu.base, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]">
<HComboboxInput <HComboboxInput
v-if="searchable" v-if="searchable"
ref="searchInput"
:display-value="() => query" :display-value="() => query"
name="q" name="q"
:placeholder="searchablePlaceholder" :placeholder="searchablePlaceholder"
autofocus autofocus
autocomplete="off" autocomplete="off"
:class="uiMenu.input" :class="uiMenu.input"
@change="query = $event.target.value" @change="onChange"
/> />
<component <component
:is="searchable ? 'HComboboxOption' : 'HListboxOption'" :is="searchable ? 'HComboboxOption' : 'HListboxOption'"
@@ -126,7 +125,7 @@
<script lang="ts"> <script lang="ts">
import { ref, computed, toRef, watch, defineComponent } from 'vue' import { ref, computed, toRef, watch, defineComponent } from 'vue'
import type { PropType, ComponentPublicInstance } from 'vue' import type { PropType } from 'vue'
import { import {
Combobox as HCombobox, Combobox as HCombobox,
ComboboxButton as HComboboxButton, ComboboxButton as HComboboxButton,
@@ -177,6 +176,10 @@ export default defineComponent({
type: [String, Number, Object, Array], type: [String, Number, Object, Array],
default: '' default: ''
}, },
query: {
type: String,
default: null
},
by: { by: {
type: String, type: String,
default: undefined default: undefined
@@ -326,7 +329,7 @@ export default defineComponent({
default: () => ({}) default: () => ({})
} }
}, },
emits: ['update:modelValue', 'open', 'close', 'change'], emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
setup (props, { emit, slots }) { setup (props, { emit, slots }) {
const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -341,8 +344,16 @@ export default defineComponent({
const size = computed(() => sizeButtonGroup.value || sizeFormGroup.value) const size = computed(() => sizeButtonGroup.value || sizeFormGroup.value)
const query = ref('') const internalQuery = ref('')
const searchInput = ref<ComponentPublicInstance<HTMLElement>>() const query = computed({
get () {
return props.query ?? internalQuery.value
},
set (value) {
internalQuery.value = value
emit('update:query', value)
}
})
const selectClass = computed(() => { const selectClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant] const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
@@ -476,17 +487,15 @@ export default defineComponent({
}) })
function onUpdate (event: any) { function onUpdate (event: any) {
if (query.value && searchInput.value?.$el) {
query.value = ''
// explicitly set input text because `ComboboxInput` `displayValue` is not reactive
searchInput.value.$el.value = ''
}
emit('update:modelValue', event) emit('update:modelValue', event)
emit('change', event) emit('change', event)
emitFormChange() emitFormChange()
} }
function onChange (event: any) {
query.value = event.target.value
}
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
@@ -512,8 +521,10 @@ export default defineComponent({
trailingWrapperIconClass, trailingWrapperIconClass,
filteredOptions, filteredOptions,
createOption, createOption,
// eslint-disable-next-line vue/no-dupe-keys
query, query,
onUpdate onUpdate,
onChange
} }
} }
}) })