feat: add useSearch composable to search in Array or AnyTreeNode

This commit is contained in:
axolotle 2024-07-09 17:05:32 +02:00
parent 3e60b96ee0
commit 501bce484a
3 changed files with 76 additions and 27 deletions

View file

@ -1,18 +1,20 @@
<script setup lang="ts" generic="T extends Obj"> <script setup lang="ts" generic="T extends Obj | AnyTreeNode">
import type { Component } from 'vue' import { computed, type Component } from 'vue'
import { useI18n } from 'vue-i18n'
import type { AnyTreeNode } from '@/helpers/data/tree'
import type { Obj } from '@/types/commons' import type { Obj } from '@/types/commons'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
items: T[] | null items?: T[] | null
itemsName: string | null itemsName: string | null
filteredItems: T[] | null modelValue?: string
search?: string
skeleton?: string | Component skeleton?: string | Component
}>(), }>(),
{ {
search: undefined, items: undefined,
modelValue: undefined,
skeleton: 'ListGroupSkeleton', skeleton: 'ListGroupSkeleton',
}, },
) )
@ -27,9 +29,21 @@ const slots = defineSlots<{
skeleton: any skeleton: any
}>() }>()
const emit = defineEmits<{ defineEmits<{
'update:search': [value: string] 'update:modelValue': [value: string]
}>() }>()
const model = defineModel<string>()
const { t } = useI18n()
const noItemsMessage = computed(() => {
if (props.items) return
return t(
props.items === undefined ? 'items_verbose_count' : 'search.not_found',
{ items: t('items.' + props.itemsName, 0) },
0,
)
})
</script> </script>
<template> <template>
@ -45,11 +59,8 @@ const emit = defineEmits<{
<BFormInput <BFormInput
id="top-bar-search" id="top-bar-search"
:modelValue="search" v-model="model"
@update:modelValue="emit('update:search', $event)" :placeholder="t('search.for', { items: t('items.' + itemsName, 2) })"
:placeholder="
$t('search.for', { items: $t('items.' + itemsName, 2) })
"
:disabled="!items" :disabled="!items"
/> />
</BInputGroup> </BInputGroup>
@ -63,22 +74,10 @@ const emit = defineEmits<{
</template> </template>
<template #default> <template #default>
<BAlert <BAlert v-if="noItemsMessage" :model-value="true" variant="warning">
v-if="items === null || filteredItems === null"
:modelValue="true"
variant="warning"
>
<slot name="alert-message"> <slot name="alert-message">
<YIcon iname="exclamation-triangle" /> <YIcon iname="exclamation-triangle" />
{{ {{ noItemsMessage }}
$t(
items === null ? 'items_verbose_count' : 'search.not_found',
{
items: $t('items.' + itemsName, 0),
},
0,
)
}}
</slot> </slot>
</BAlert> </BAlert>

View file

@ -0,0 +1,46 @@
import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue'
import { computed, ref, toValue, watch } from 'vue'
import type { AnyTreeNode, TreeRootNode } from '@/helpers/data/tree'
export function useSearch<
T extends any[] | TreeRootNode,
V extends T extends (infer V)[] ? V : AnyTreeNode,
>(
items: MaybeRefOrGetter<T | undefined> | ComputedRef<T | undefined>,
filterFn: (search: string, item: V, index: number, arr: T) => boolean,
{
externalSearch,
filterIfNoSearch = false,
filterAllFn,
}: {
filterAllFn?: (search: string, items: T) => boolean | undefined
filterIfNoSearch?: boolean
externalSearch?: MaybeRefOrGetter<string>
} = {},
): [search: Ref<string>, filteredItems: ComputedRef<T | undefined | null>] {
const search = ref(toValue(externalSearch) ?? '')
watch(
() => toValue(externalSearch),
(s) => (search.value = s ?? ''),
)
const filteredItems = computed(() => {
const items_ = toValue(items)
const s = toValue(search.value).toLowerCase()
if (!items_) return undefined
if (filterAllFn) {
const returnAll = filterAllFn(s, items_)
if (returnAll !== undefined) {
return returnAll ? items_ : undefined
}
}
if (!s && !filterIfNoSearch) return items_
const filteredItems_ = items_.filter((...args) =>
filterFn(s, ...(args as [V, number, T])),
) as T
return filteredItems_.length ? filteredItems_ : null
})
return [search, filteredItems]
}

View file

@ -106,6 +106,10 @@ class TreeNode {
} }
}) })
} }
get length(): number {
return this.children.length
}
} }
export class TreeRootNode extends TreeNode { export class TreeRootNode extends TreeNode {