From 1ef977fb8c3a754a909a82733f31b1b45577f021 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Thu, 22 Aug 2024 15:41:49 +0200 Subject: [PATCH] feat(CommandPalette): handle `filter` false and `postFilter` --- src/runtime/components/CommandPalette.vue | 62 +++++++++++++++-------- src/runtime/components/SelectMenu.vue | 2 +- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/runtime/components/CommandPalette.vue b/src/runtime/components/CommandPalette.vue index 1285f86b..183c0cf4 100644 --- a/src/runtime/components/CommandPalette.vue +++ b/src/runtime/components/CommandPalette.vue @@ -31,8 +31,14 @@ export interface CommandPaletteGroup { 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(searchTerm, items, fuse) -const groups = computed(() => { - if (!fuseResults.value?.length) { - return [] +function getGroupWithItems(group: G, items: (T & { matches?: FuseResult['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(item, searchTerm.value, 'label'), + suffixHtml: highlight(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['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(item, searchTerm.value, 'label'), - suffixHtml: highlight(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 + ] }) diff --git a/src/runtime/components/SelectMenu.vue b/src/runtime/components/SelectMenu.vue index eb3785c0..3a59e93e 100644 --- a/src/runtime/components/SelectMenu.vue +++ b/src/runtime/components/SelectMenu.vue @@ -68,7 +68,7 @@ export interface SelectMenuProps extends Pick, '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[]