diff --git a/docs/pages/examples.vue b/docs/pages/examples.vue
index 3ceb42d691..0c7a4a47a9 100644
--- a/docs/pages/examples.vue
+++ b/docs/pages/examples.vue
@@ -149,18 +149,13 @@
@@ -263,6 +258,18 @@ const y = ref(0)
const isContextMenuOpen = ref(false)
const contextMenuRef = ref(null)
+const commandPaletteGroups = computed(() => ([{
+ key: 'people',
+ commands: people.value
+}, {
+ key: 'search',
+ label: q => q && `Search results for "${q}"...`,
+ search: async (q) => {
+ if (!q) { return [] }
+ return await $fetch(`https://jsonplaceholder.typicode.com/users?q=${q}`)
+ }
+}]))
+
onMounted(() => {
document.addEventListener('mousemove', ({ clientX, clientY }) => {
x.value = clientX
diff --git a/src/runtime/components/navigation/CommandPalette.vue b/src/runtime/components/navigation/CommandPalette.vue
index de658dfb02..07af7a87d8 100644
--- a/src/runtime/components/navigation/CommandPalette.vue
+++ b/src/runtime/components/navigation/CommandPalette.vue
@@ -38,7 +38,14 @@
aria-label="Commands"
class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2"
>
-
+
@@ -59,6 +66,7 @@
import { ref, computed, watch, onMounted } from 'vue'
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
+import { useDebounceFn } from '@vueuse/core'
import { useFuse } from '@vueuse/integrations/useFuse'
import { groupBy, map } from 'lodash-es'
import { defu } from 'defu'
@@ -133,6 +141,10 @@ const props = defineProps({
placeholder: {
type: Boolean,
default: true
+ },
+ debounce: {
+ type: Number,
+ default: 0
}
})
@@ -168,29 +180,44 @@ const options: ComputedRef>> = computed(() => de
matchAllWhenSearchEmpty: true
}))
-const commands = computed(() => props.groups.reduce((acc, group) => {
+const commands = computed(() => props.groups.filter(group => !group.search).reduce((acc, group) => {
return acc.concat(group.commands.map(command => ({ ...command, group: group.key })))
}, [] as Command[]))
+const searchResults = ref({})
+
const { results } = useFuse(query, commands, options)
-const groups = computed(() => map(groupBy(results.value, command => command.item.group), (results, key) => {
- const commands = results.map((result) => {
- const { item, ...data } = result
+const groups = computed(() => ([
+ ...map(groupBy(results.value, command => command.item.group), (results, key) => {
+ const commands = results.map((result) => {
+ const { item, ...data } = result
+
+ return {
+ ...item,
+ ...data
+ }
+ })
return {
- ...item,
- ...data
- }
- })
+ ...props.groups.find(group => group.key === key),
+ commands: commands.slice(0, options.value.resultLimit)
+ } as Group
+ }),
+ ...props.groups.filter(group => !!group.search).map(group => ({ ...group, commands: (searchResults.value[group.key] || []).slice(0, options.value.resultLimit) })).filter(group => group.commands.length)
+]))
- return {
- ...props.groups.find(group => group.key === key),
- commands: commands.slice(0, options.value.resultLimit)
- } as Group
-}))
+const debouncedSearch = useDebounceFn(async () => {
+ const searchableGroups = props.groups.filter(group => !!group.search)
+
+ await Promise.all(searchableGroups.map(async (group) => {
+ searchResults.value[group.key] = await group.search(query.value)
+ }))
+}, props.debounce)
watch(query, () => {
+ debouncedSearch()
+
// Select first item on search changes
setTimeout(() => {
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804
diff --git a/src/runtime/components/navigation/CommandPaletteGroup.vue b/src/runtime/components/navigation/CommandPaletteGroup.vue
index 3e55fee73a..b598613d29 100644
--- a/src/runtime/components/navigation/CommandPaletteGroup.vue
+++ b/src/runtime/components/navigation/CommandPaletteGroup.vue
@@ -1,7 +1,7 @@
-
- {{ group[groupAttribute] }}
+
+ {{ label }}
@@ -52,6 +52,7 @@