feat(CommandPalette): handle filter false and postFilter

This commit is contained in:
Benjamin Canac
2024-08-22 15:41:49 +02:00
parent d558b3e29c
commit 1ef977fb8c
2 changed files with 43 additions and 21 deletions

View File

@@ -31,8 +31,14 @@ export interface CommandPaletteGroup<T> {
label?: string
slot?: string
items?: T[]
/** Filter group items based on the search term. */
filter?: (searchTerm: string, items: T[]) => T[]
/**
* Wether to filter group items with [useFuse](https://vueuse.org/integrations/useFuse).
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
* @defaultValue true
*/
filter?: boolean
/** Filter group items after the search happened. */
postFilter?: (searchTerm: string, items: T[]) => T[]
/** The icon displayed when an item is highlighted. */
highlightedIcon?: string
}
@@ -140,16 +146,33 @@ const items = computed(() => props.groups?.filter((group) => {
return false
}
if (group.filter === false) {
return false
}
return true
}).flatMap(group => group.items?.map(item => ({ ...item, group: group.id })) || []) || [])
const { results: fuseResults } = useFuse<typeof items.value[number]>(searchTerm, items, fuse)
const groups = computed(() => {
if (!fuseResults.value?.length) {
return []
function getGroupWithItems(group: G, items: (T & { matches?: FuseResult<T>['matches'] })[]) {
if (group?.postFilter && typeof group.postFilter === 'function') {
items = group.postFilter(searchTerm.value, items)
}
return {
...group,
items: items.slice(0, fuse.value.resultLimit).map((item) => {
return {
...item,
labelHtml: highlight<T>(item, searchTerm.value, 'label'),
suffixHtml: highlight<T>(item, searchTerm.value, undefined, ['label'])
}
})
}
}
const groups = computed(() => {
const groupsById = fuseResults.value.reduce((acc, result) => {
const { item, matches } = result
if (!item.group) {
@@ -162,24 +185,23 @@ const groups = computed(() => {
return acc
}, {} as Record<string, (T & { matches?: FuseResult<T>['matches'] })[]>)
return Object.entries(groupsById).map(([id, items]) => {
const fuseGroups = Object.entries(groupsById).map(([id, items]) => {
const group = props.groups?.find(group => group.id === id)
if (group?.filter && typeof group.filter === 'function') {
items = group.filter(searchTerm.value, items)
if (!group) {
return
}
return {
...group,
items: items.slice(0, fuse.value.resultLimit).map((item) => {
return {
...item,
labelHtml: highlight<T>(item, searchTerm.value, 'label'),
suffixHtml: highlight<T>(item, searchTerm.value, undefined, ['label'])
}
})
}
})
return getGroupWithItems(group, items)
}).filter(group => !!group)
const nonFuseGroups = props.groups?.filter(group => group.filter === false && group.items?.length).map((group) => {
return getGroupWithItems(group, group.items || [])
}) || []
return [
...fuseGroups,
...nonFuseGroups
]
})
</script>

View File

@@ -68,7 +68,7 @@ export interface SelectMenuProps<T> extends Pick<ComboboxRootProps<T>, 'modelVal
portal?: boolean
/**
* Whether to filter items or not, can be an array of fields to filter.
* When `false`, items will not be filtered which is useful for custom filtering.
* When `false`, items will not be filtered which is useful for custom filtering (useAsyncData, useFetch, etc.).
* @defaultValue ['label']
*/
filter?: boolean | string[]