diff --git a/docs/components/content/examples/SelectMenuExampleAsyncSearch.vue b/docs/components/content/examples/SelectMenuExampleAsyncSearch.vue
new file mode 100644
index 00000000..77fa206f
--- /dev/null
+++ b/docs/components/content/examples/SelectMenuExampleAsyncSearch.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/docs/content/3.forms/4.select-menu.md b/docs/content/3.forms/4.select-menu.md
index 26116650..ed48128a 100644
--- a/docs/content/3.forms/4.select-menu.md
+++ b/docs/content/3.forms/4.select-menu.md
@@ -149,6 +149,40 @@ props:
---
::
+### Async search
+
+Pass a function to the `searchable` prop to customize the search behavior and filter options according to your needs. The function will receive the query as its first argument and should return an array.
+
+Use the `debounce` prop to adjust the delay of the function.
+
+::component-example
+#default
+:select-menu-example-async-search{class="max-w-[12rem] w-full"}
+
+#code
+```vue
+
+
+
+
+
+```
+::
+
## Slots
### `label`
diff --git a/src/runtime/components/forms/SelectMenu.vue b/src/runtime/components/forms/SelectMenu.vue
index 5bdf6b61..6398aaba 100644
--- a/src/runtime/components/forms/SelectMenu.vue
+++ b/src/runtime/components/forms/SelectMenu.vue
@@ -129,6 +129,7 @@ import {
ListboxOptions as HListboxOptions,
ListboxOption as HListboxOption
} from '@headlessui/vue'
+import { computedAsync, useDebounceFn } from '@vueuse/core'
import { defu } from 'defu'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
@@ -219,13 +220,17 @@ export default defineComponent({
default: false
},
searchable: {
- type: Boolean,
+ type: [Boolean, Function] as PropType Promise | any[])>,
default: false
},
searchablePlaceholder: {
type: String,
default: 'Search...'
},
+ debounce: {
+ type: Number,
+ default: 200
+ },
creatable: {
type: Boolean,
default: false
@@ -373,15 +378,23 @@ export default defineComponent({
)
})
- const filteredOptions = computed(() =>
- query.value === ''
- ? props.options
- : (props.options as any[]).filter((option: any) => {
- return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
- return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
- })
- })
- )
+ const debouncedSearch = typeof props.searchable === 'function' ? useDebounceFn(props.searchable, props.debounce) : undefined
+
+ const filteredOptions = computedAsync(async () => {
+ if (props.searchable && debouncedSearch) {
+ return await debouncedSearch(query.value)
+ }
+
+ if (query.value === '') {
+ return props.options
+ }
+
+ return (props.options as any[]).filter((option: any) => {
+ return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
+ return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
+ })
+ })
+ })
const queryOption = computed(() => {
return query.value === '' ? null : { [props.optionAttribute]: query.value }