feat(CommandPalette): improve theme and performance

This commit is contained in:
Benjamin Canac
2024-06-07 16:01:30 +02:00
parent 4e17da21f3
commit 20476f4b9a
4 changed files with 24 additions and 20 deletions

View File

@@ -102,7 +102,6 @@ defineShortcuts({
:groups="groups" :groups="groups"
:fuse="{ :fuse="{
fuseOptions: { fuseOptions: {
threshold: 0.1,
includeMatches: true includeMatches: true
} }
}" }"

View File

@@ -99,6 +99,7 @@ const ui = computed(() => tv({ extend: commandPalette, slots: props.ui })())
const fuse = computed(() => defu({}, props.fuse, { const fuse = computed(() => defu({}, props.fuse, {
fuseOptions: { fuseOptions: {
ignoreLocation: true, ignoreLocation: true,
threshold: 0.1,
keys: ['label', 'suffix'] keys: ['label', 'suffix']
}, },
resultLimit: 12, resultLimit: 12,
@@ -112,13 +113,7 @@ const items = computed(() => props.groups?.filter((group) => {
} }
return true return true
}).flatMap((group) => { }).flatMap(group => group.items?.map(item => ({ ...item, group: group.id })) || []) || [])
let items = group.items || []
if (group.filter) {
items = group.filter(searchTerm.value, items)
}
return items?.map(item => ({ ...item, group: group.id })) || []
}) || [])
const { results: fuseResults } = useFuse<T>(searchTerm, items, fuse) const { results: fuseResults } = useFuse<T>(searchTerm, items, fuse)
@@ -127,7 +122,7 @@ const groups = computed(() => {
return [] return []
} }
const groups: Record<string, (T & { matches: FuseResult<T>['matches'] })[]> = fuseResults.value.reduce((acc, result) => { const groupsById: Record<string, (T & { matches?: FuseResult<T>['matches'] })[]> = fuseResults.value.reduce((acc, result) => {
const { item, matches } = result const { item, matches } = result
if (!item.group) { if (!item.group) {
return acc return acc
@@ -139,12 +134,22 @@ const groups = computed(() => {
return acc return acc
}, {}) }, {})
return Object.entries(groups).map(([id, items]) => { return Object.entries(groupsById).map(([id, items]) => {
const group = props.groups?.find(group => group.id === id) const group = props.groups?.find(group => group.id === id)
if (group?.filter && typeof group.filter === 'function') {
items = group.filter(searchTerm.value, items)
}
return { return {
...group, ...group,
items: items.slice(0, fuse.value.resultLimit) 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'])
}
})
} }
}) })
}) })
@@ -180,7 +185,7 @@ const groups = computed(() => {
</UInput> </UInput>
</ComboboxInput> </ComboboxInput>
<ComboboxPortal :disabled="true"> <ComboboxPortal disabled>
<ComboboxContent :class="ui.content()" :dismissable="false"> <ComboboxContent :class="ui.content()" :dismissable="false">
<ComboboxEmpty :class="ui.empty()"> <ComboboxEmpty :class="ui.empty()">
<slot name="empty" :search-term="searchTerm"> <slot name="empty" :search-term="searchTerm">
@@ -197,7 +202,7 @@ const groups = computed(() => {
<ComboboxItem <ComboboxItem
v-for="(item, index) in group.items" v-for="(item, index) in group.items"
:key="`group-${groupIndex}-${index}`" :key="`group-${groupIndex}-${index}`"
:value="omit(item, ['matches' as any, 'group' as any, 'select'])" :value="omit(item, ['matches' as any, 'group' as any, 'select', 'labelHtml', 'suffixHtml'])"
:disabled="item.disabled" :disabled="item.disabled"
:class="ui.item()" :class="ui.item()"
@select="item.select" @select="item.select"
@@ -220,9 +225,9 @@ const groups = computed(() => {
<slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index"> <slot :name="item.slot ? `${item.slot}-label` : group.slot ? `${group.slot}-label` : `item-label`" :item="item" :index="index">
<span v-if="item.prefix" :class="ui.itemLabelPrefix()">{{ item.prefix }}</span> <span v-if="item.prefix" :class="ui.itemLabelPrefix()">{{ item.prefix }}</span>
<span :class="ui.itemLabelBase()" v-html="highlight<T>(item, searchTerm, 'label') || item.label" /> <span :class="ui.itemLabelBase()" v-html="item.labelHtml || item.label" />
<span :class="ui.itemLabelSuffix()" v-html="highlight<T>(item, searchTerm, undefined, ['label']) || item.suffix" /> <span :class="ui.itemLabelSuffix()" v-html="item.suffixHtml || item.suffix" />
</slot> </slot>
</span> </span>

View File

@@ -54,9 +54,9 @@ export function highlight<T>(item: T & { matches?: FuseResult<T>['matches'] }, s
content += value.substring(nextUnhighlightedRegionStartingIndex) content += value.substring(nextUnhighlightedRegionStartingIndex)
const endIndex = content.indexOf('</mark>') const markIndex = content.indexOf('<mark>')
if (endIndex > 50) { if (markIndex !== -1) {
content = truncateHTMLFromStart(content, 45) content = truncateHTMLFromStart(content, content.length - markIndex)
} }
return content return content

View File

@@ -19,8 +19,8 @@ export default (options: Required<ModuleOptions>) => ({
itemTrailingHighlightedIcon: 'shrink-0 size-5 text-gray-400 dark:text-gray-500 hidden group-data-highlighted:inline-flex', itemTrailingHighlightedIcon: 'shrink-0 size-5 text-gray-400 dark:text-gray-500 hidden group-data-highlighted:inline-flex',
itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5',
itemLabel: 'truncate space-x-1', itemLabel: 'truncate space-x-1',
itemLabelBase: '[&>mark]:text-[initial] [&>mark]:bg-[initial]', itemLabelBase: 'text-gray-900 dark:text-white [&>mark]:bg-primary-500 dark:[&>mark]:bg-primary-400 [&>mark]:text-white dark:[&>mark]:text-gray-900',
itemLabelPrefix: 'text-gray-400 dark:text-gray-500', itemLabelPrefix: 'text-gray-700 dark:text-gray-200',
itemLabelSuffix: 'text-gray-400 dark:text-gray-500 [&>mark]:bg-primary-500 dark:[&>mark]:bg-primary-400 [&>mark]:text-white dark:[&>mark]:text-gray-900' itemLabelSuffix: 'text-gray-400 dark:text-gray-500 [&>mark]:bg-primary-500 dark:[&>mark]:bg-primary-400 [&>mark]:text-white dark:[&>mark]:text-gray-900'
} }
}) })