Skip to content

Commit e751b37

Browse files
fix(CommandPalette/ContentSearch): improve performances and filtering logic (#5433)
1 parent e346072 commit e751b37

File tree

14 files changed

+868
-439
lines changed

14 files changed

+868
-439
lines changed

docs/app/app.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const appConfig = useAppConfig()
99
const colorMode = useColorMode()
1010
1111
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs', ['framework', 'category', 'description']))
12-
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
12+
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs', {
13+
ignoredTags: ['style']
14+
}), {
1315
server: false
1416
})
1517

docs/app/composables/useSearch.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,21 @@ export function useSearch() {
101101
id: 'ai',
102102
label: 'AI',
103103
ignoreFilter: true,
104-
items: searchTerm.value
105-
? [{
106-
label: `Ask AI for “${searchTerm.value}”`,
107-
icon: 'i-lucide-bot',
108-
ui: {
109-
itemLeadingIcon: 'group-data-highlighted:not-group-data-disabled:text-primary'
110-
},
111-
onSelect
112-
}]
113-
: []
104+
postFilter: (searchTerm: string, items: any[]) => {
105+
if (!searchTerm) {
106+
return []
107+
}
108+
109+
return items
110+
},
111+
items: [{
112+
label: 'Ask AI',
113+
icon: 'i-lucide-bot',
114+
ui: {
115+
itemLeadingIcon: 'group-data-highlighted:not-group-data-disabled:text-primary'
116+
},
117+
onSelect
118+
}]
114119
}, {
115120
id: 'framework',
116121
label: 'Framework',

docs/app/error.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ const appConfig = useAppConfig()
1111
const colorMode = useColorMode()
1212
1313
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs', ['framework', 'category', 'description']))
14-
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
14+
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs', {
15+
ignoredTags: ['style']
16+
}), {
1517
server: false
1618
})
1719

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@
162162
"vue-component-type-helpers": "^3.1.3"
163163
},
164164
"devDependencies": {
165-
"@nuxt/content": "^3.8.0",
166165
"@nuxt/eslint-config": "^1.10.0",
167166
"@nuxt/module-builder": "^1.0.2",
168167
"@nuxt/test-utils": "^3.20.1",
@@ -181,6 +180,7 @@
181180
},
182181
"peerDependencies": {
183182
"@inertiajs/vue3": "^2.0.7",
183+
"@nuxt/content": "^3.0.0",
184184
"joi": "^18.0.0",
185185
"superstruct": "^2.0.0",
186186
"typescript": "^5.6.3",
@@ -193,6 +193,9 @@
193193
"@inertiajs/vue3": {
194194
"optional": true
195195
},
196+
"@nuxt/content": {
197+
"optional": true
198+
},
196199
"joi": {
197200
"optional": true
198201
},

pnpm-lock.yaml

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/runtime/components/CommandPalette.vue

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export type CommandPaletteSlots<G extends CommandPaletteGroup<T> = CommandPalett
199199
import { computed, ref, useTemplateRef, toRef } from 'vue'
200200
import { ListboxRoot, ListboxFilter, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxVirtualizer, ListboxItem, ListboxItemIndicator, useForwardProps, useForwardPropsEmits } from 'reka-ui'
201201
import { defu } from 'defu'
202-
import { reactivePick, createReusableTemplate } from '@vueuse/core'
202+
import { reactivePick, createReusableTemplate, refThrottled } from '@vueuse/core'
203203
import { useFuse } from '@vueuse/integrations/useFuse'
204204
import { useAppConfig } from '#imports'
205205
import { useLocale } from '../composables/useLocale'
@@ -290,14 +290,18 @@ const items = computed(() => groups.value?.filter((group) => {
290290
291291
const { results: fuseResults } = useFuse<typeof items.value[number]>(searchTerm, items, fuse)
292292
293-
function getGroupWithItems(group: G, items: (T & { matches?: FuseResult<T>['matches'] })[]) {
293+
const throttledFuseResults = refThrottled(fuseResults, 16, true)
294+
295+
function processGroupItems(group: G, items: (T & { matches?: FuseResult<T>['matches'] })[]) {
296+
let processedItems = items
297+
294298
if (group?.postFilter && typeof group.postFilter === 'function') {
295-
items = group.postFilter(searchTerm.value, items)
299+
processedItems = group.postFilter(searchTerm.value, processedItems)
296300
}
297301
298302
return {
299303
...group,
300-
items: items.slice(0, fuse.value.resultLimit).map((item) => {
304+
items: processedItems.slice(0, fuse.value.resultLimit).map((item) => {
301305
return {
302306
...item,
303307
labelHtml: highlight<T>(item, searchTerm.value, props.labelKey),
@@ -308,7 +312,9 @@ function getGroupWithItems(group: G, items: (T & { matches?: FuseResult<T>['matc
308312
}
309313
310314
const filteredGroups = computed(() => {
311-
const groupsById = fuseResults.value.reduce((acc, result) => {
315+
const currentGroups = groups.value
316+
317+
const groupsById = throttledFuseResults.value.reduce((acc, result) => {
312318
const { item, matches } = result
313319
if (!item.group) {
314320
return acc
@@ -321,38 +327,49 @@ const filteredGroups = computed(() => {
321327
}, {} as Record<string, (T & { matches?: FuseResult<T>['matches'] })[]>)
322328
323329
if (props.preserveGroupOrder) {
324-
const processedGroups: Array<ReturnType<typeof getGroupWithItems>> = []
330+
const processedGroups: Array<ReturnType<typeof processGroupItems>> = []
325331
326-
for (const group of groups.value || []) {
332+
for (const group of currentGroups || []) {
327333
if (!group.items?.length) {
328334
continue
329335
}
330336
331-
const items = group.ignoreFilter
332-
? group.items
333-
: groupsById[group.id]
337+
const items = group.ignoreFilter ? group.items : groupsById[group.id]
338+
if (!items?.length) {
339+
continue
340+
}
341+
342+
const processedGroup = processGroupItems(group, items)
334343
335-
if (items?.length) {
336-
processedGroups.push(getGroupWithItems(group, items))
344+
// Filter out groups that become empty after postFilter
345+
if (processedGroup.items?.length) {
346+
processedGroups.push(processedGroup)
337347
}
338348
}
339349
340350
return processedGroups
341351
}
342352
343353
const fuseGroups = Object.entries(groupsById).map(([id, items]) => {
344-
const group = groups.value?.find(group => group.id === id)
354+
const group = currentGroups?.find(group => group.id === id)
345355
if (!group) {
346356
return
347357
}
348358
349-
return getGroupWithItems(group, items)
359+
const processedGroup = processGroupItems(group, items)
360+
// Filter out groups without items after postFilter
361+
return processedGroup.items?.length ? processedGroup : undefined
350362
}).filter(group => !!group)
351363
352-
const nonFuseGroups = groups.value
364+
const nonFuseGroups = currentGroups
353365
?.map((group, index) => ({ ...group, index }))
354366
?.filter(group => group.ignoreFilter && group.items?.length)
355-
?.map(group => ({ ...getGroupWithItems(group, group.items || []), index: group.index })) || []
367+
?.map((group) => {
368+
const processedGroup = processGroupItems(group, group.items || [])
369+
return { ...processedGroup, index: group.index }
370+
})
371+
// Filter out groups without items after postFilter
372+
?.filter(group => group.items?.length) || []
356373
357374
return nonFuseGroups.reduce((acc, group) => {
358375
acc.splice(group.index, 0, group)
@@ -442,9 +459,11 @@ function onSelect(e: Event, item: T) {
442459
<slot :name="((item.slot ? `${item.slot}-label` : group?.slot ? `${group.slot}-label` : `item-label`) as keyof CommandPaletteSlots<G, T>)" :item="(item as any)" :index="index" :ui="ui">
443460
<span v-if="item.prefix" :class="ui.itemLabelPrefix({ class: [props.ui?.itemLabelPrefix, item.ui?.itemLabelPrefix] })">{{ item.prefix }}</span>
444461

445-
<span :class="ui.itemLabelBase({ class: [props.ui?.itemLabelBase, item.ui?.itemLabelBase], active: active || item.active })" v-html="item.labelHtml || get(item, props.labelKey as string)" />
462+
<span v-if="item.labelHtml" :class="ui.itemLabelBase({ class: [props.ui?.itemLabelBase, item.ui?.itemLabelBase], active: active || item.active })" v-html="item.labelHtml" />
463+
<span v-else :class="ui.itemLabelBase({ class: [props.ui?.itemLabelBase, item.ui?.itemLabelBase], active: active || item.active })">{{ get(item, props.labelKey as string) }}</span>
446464

447-
<span :class="ui.itemLabelSuffix({ class: [props.ui?.itemLabelSuffix, item.ui?.itemLabelSuffix], active: active || item.active })" v-html="item.suffixHtml || item.suffix" />
465+
<span v-if="item.suffixHtml" :class="ui.itemLabelSuffix({ class: [props.ui?.itemLabelSuffix, item.ui?.itemLabelSuffix], active: active || item.active })" v-html="item.suffixHtml" />
466+
<span v-else-if="item.suffix" :class="ui.itemLabelSuffix({ class: [props.ui?.itemLabelSuffix, item.ui?.itemLabelSuffix], active: active || item.active })">{{ item.suffix }}</span>
448467
</slot>
449468
</span>
450469

0 commit comments

Comments
 (0)