|
|
@@ -41,7 +41,7 @@ et sélectionner des éléments organisés en catégories et sous-catégories.
|
|
|
@input="onSearchInput"
|
|
|
@click:clear.stop="onSearchClear"
|
|
|
/>
|
|
|
- <v-divider class="mt-2"/>
|
|
|
+ <v-divider class="mt-2" />
|
|
|
</template>
|
|
|
|
|
|
<template #selection="{ item, index }">
|
|
|
@@ -52,10 +52,18 @@ et sélectionner des éléments organisés en catégories et sous-catégories.
|
|
|
@click:close="removeItem(item.raw.value!)"
|
|
|
>
|
|
|
<!-- Use the label from the item if available, otherwise use the mapping -->
|
|
|
- {{ item.raw.label && item.raw.label !== item.raw.value ? item.raw.label : selectedItemsMap[item.raw.value] || item.raw.value }}
|
|
|
+ {{
|
|
|
+ item.raw.label && item.raw.label !== item.raw.value
|
|
|
+ ? item.raw.label
|
|
|
+ : selectedItemsMap[item.raw.value] || item.raw.value
|
|
|
+ }}
|
|
|
</v-chip>
|
|
|
<span
|
|
|
- v-if="maxVisibleChips && index === maxVisibleChips && modelValue.length > maxVisibleChips"
|
|
|
+ v-if="
|
|
|
+ maxVisibleChips &&
|
|
|
+ index === maxVisibleChips &&
|
|
|
+ modelValue.length > maxVisibleChips
|
|
|
+ "
|
|
|
class="text-grey text-caption align-self-center"
|
|
|
>
|
|
|
(+{{ modelValue.length - maxVisibleChips }} {{ $t('others') }})
|
|
|
@@ -65,13 +73,20 @@ et sélectionner des éléments organisés en catégories et sous-catégories.
|
|
|
<template #item="{ item }">
|
|
|
<template v-if="item.raw.type === 'category'">
|
|
|
<v-list-item
|
|
|
- @click.stop="toggleCategory(item.raw.id)"
|
|
|
:ripple="false"
|
|
|
- :class="{ 'v-list-item--active': expandedCategories.has(item.raw.id) }"
|
|
|
+ :class="{
|
|
|
+ 'v-list-item--active': expandedCategories.has(item.raw.id),
|
|
|
+ }"
|
|
|
+ @click.stop="toggleCategory(item.raw.id)"
|
|
|
>
|
|
|
<template #prepend>
|
|
|
<v-icon
|
|
|
- :icon="'fas ' + (expandedCategories.has(item.raw.id) ? 'fa-angle-down' : 'fa-angle-right')"
|
|
|
+ :icon="
|
|
|
+ 'fas ' +
|
|
|
+ (expandedCategories.has(item.raw.id)
|
|
|
+ ? 'fa-angle-down'
|
|
|
+ : 'fa-angle-right')
|
|
|
+ "
|
|
|
size="small"
|
|
|
/>
|
|
|
</template>
|
|
|
@@ -83,16 +98,21 @@ et sélectionner des éléments organisés en catégories et sous-catégories.
|
|
|
|
|
|
<template v-else-if="item.raw.type === 'subcategory'">
|
|
|
<v-list-item
|
|
|
- @click.stop="toggleSubcategory(item.raw.id)"
|
|
|
:ripple="false"
|
|
|
:class="{
|
|
|
'v-list-item--active': expandedSubcategories.has(item.raw.id),
|
|
|
- 'pl-8': true
|
|
|
+ 'pl-8': true,
|
|
|
}"
|
|
|
+ @click.stop="toggleSubcategory(item.raw.id)"
|
|
|
>
|
|
|
<template #prepend>
|
|
|
<v-icon
|
|
|
- :icon="'fas ' + (expandedSubcategories.has(item.raw.id) ? 'fa-angle-down' : 'fa-angle-right')"
|
|
|
+ :icon="
|
|
|
+ 'fas ' +
|
|
|
+ (expandedSubcategories.has(item.raw.id)
|
|
|
+ ? 'fa-angle-down'
|
|
|
+ : 'fa-angle-right')
|
|
|
+ "
|
|
|
size="small"
|
|
|
/>
|
|
|
</template>
|
|
|
@@ -104,16 +124,19 @@ et sélectionner des éléments organisés en catégories et sous-catégories.
|
|
|
|
|
|
<template v-else>
|
|
|
<v-list-item
|
|
|
- @click="toggleItem(item.raw.value!)"
|
|
|
:active="modelValue.includes(item.raw.value!)"
|
|
|
- :class="{ 'pl-12': item.raw.level === 2, 'pl-8': item.raw.level === 1 }"
|
|
|
+ :class="{
|
|
|
+ 'pl-12': item.raw.level === 2,
|
|
|
+ 'pl-8': item.raw.level === 1,
|
|
|
+ }"
|
|
|
+ @click="toggleItem(item.raw.value!)"
|
|
|
>
|
|
|
<template #prepend>
|
|
|
<v-checkbox
|
|
|
:model-value="modelValue.includes(item.raw.value!)"
|
|
|
- @click.stop="toggleItem(item.raw.value!)"
|
|
|
color="primary"
|
|
|
:hide-details="true"
|
|
|
+ @click.stop="toggleItem(item.raw.value!)"
|
|
|
/>
|
|
|
</template>
|
|
|
<v-list-item-title>
|
|
|
@@ -140,17 +163,17 @@ interface SelectItem {
|
|
|
const props = defineProps({
|
|
|
modelValue: {
|
|
|
type: Array as PropType<string[]>,
|
|
|
- required: true
|
|
|
+ required: true,
|
|
|
},
|
|
|
items: {
|
|
|
type: Array as PropType<SelectItem[]>,
|
|
|
- required: true
|
|
|
+ required: true,
|
|
|
},
|
|
|
maxVisibleChips: {
|
|
|
type: Number,
|
|
|
required: false,
|
|
|
- default: null
|
|
|
- }
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
})
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
@@ -181,10 +204,10 @@ const toggleCategory = (categoryId: string) => {
|
|
|
if (expandedCategories.value.has(categoryId)) {
|
|
|
expandedCategories.value.delete(categoryId)
|
|
|
// Fermer aussi les sous-catégories
|
|
|
- const subcategories = props.items.filter(i =>
|
|
|
- i.parentId === categoryId && i.type === 'subcategory'
|
|
|
+ const subcategories = props.items.filter(
|
|
|
+ (i) => i.parentId === categoryId && i.type === 'subcategory',
|
|
|
)
|
|
|
- subcategories.forEach(sub => {
|
|
|
+ subcategories.forEach((sub) => {
|
|
|
expandedSubcategories.value.delete(sub.id)
|
|
|
})
|
|
|
} else {
|
|
|
@@ -232,7 +255,10 @@ const toggleItem = (value: string) => {
|
|
|
* after the specified item has been removed.
|
|
|
*/
|
|
|
const removeItem = (value: string) => {
|
|
|
- emit('update:modelValue', props.modelValue.filter(item => item !== value))
|
|
|
+ emit(
|
|
|
+ 'update:modelValue',
|
|
|
+ props.modelValue.filter((item) => item !== value),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -245,21 +271,20 @@ const onSearchInput = () => {
|
|
|
|
|
|
if (searchText.value.trim()) {
|
|
|
// Trouver tous les éléments qui correspondent à la recherche
|
|
|
- const matchingItems = props.items.filter(item =>
|
|
|
- item.type === 'item' &&
|
|
|
- item.level === 2 &&
|
|
|
- itemMatchesSearch(item)
|
|
|
+ const matchingItems = props.items.filter(
|
|
|
+ (item) =>
|
|
|
+ item.type === 'item' && item.level === 2 && itemMatchesSearch(item),
|
|
|
)
|
|
|
|
|
|
// Pour chaque élément correspondant, ajouter ses parents aux ensembles d'expansion
|
|
|
for (const item of matchingItems) {
|
|
|
// Trouver et ajouter la sous-catégorie parente
|
|
|
- const subcategory = props.items.find(i => i.id === item.parentId)
|
|
|
+ const subcategory = props.items.find((i) => i.id === item.parentId)
|
|
|
if (subcategory) {
|
|
|
expandedSubcategories.value.add(subcategory.id)
|
|
|
|
|
|
// Trouver et ajouter la catégorie parente
|
|
|
- const category = props.items.find(i => i.id === subcategory.parentId)
|
|
|
+ const category = props.items.find((i) => i.id === subcategory.parentId)
|
|
|
if (category) {
|
|
|
expandedCategories.value.add(category.id)
|
|
|
}
|
|
|
@@ -280,12 +305,15 @@ const onSearchClear = () => {
|
|
|
* @param {string} normalizedSearch - The normalized search term.
|
|
|
* @returns {boolean} `true` if any word in the text starts with the search term; otherwise, `false`.
|
|
|
*/
|
|
|
-const anyWordStartsWith = (normalizedText: string, normalizedSearch: string): boolean => {
|
|
|
+const anyWordStartsWith = (
|
|
|
+ normalizedText: string,
|
|
|
+ normalizedSearch: string,
|
|
|
+): boolean => {
|
|
|
// Split the text into words
|
|
|
const words = normalizedText.split(' ')
|
|
|
|
|
|
// Check if any word starts with the search term
|
|
|
- return words.some(word => word.startsWith(normalizedSearch))
|
|
|
+ return words.some((word) => word.startsWith(normalizedSearch))
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -315,16 +343,31 @@ const itemMatchesSearch = (item: SelectItem): boolean => {
|
|
|
// Si c'est un élément de niveau 2, vérifier son label et les labels de ses parents
|
|
|
if (item.type === 'item' && item.level === 2) {
|
|
|
// Vérifier le label de l'élément
|
|
|
- if (anyWordStartsWith(StringUtils.normalize(item.label), normalizedSearch)) return true
|
|
|
+ if (anyWordStartsWith(StringUtils.normalize(item.label), normalizedSearch))
|
|
|
+ return true
|
|
|
|
|
|
// Trouver et vérifier le label de la sous-catégorie parente
|
|
|
- const subcategory = props.items.find(i => i.id === item.parentId)
|
|
|
- if (subcategory && anyWordStartsWith(StringUtils.normalize(subcategory.label), normalizedSearch)) return true
|
|
|
+ const subcategory = props.items.find((i) => i.id === item.parentId)
|
|
|
+ if (
|
|
|
+ subcategory &&
|
|
|
+ anyWordStartsWith(
|
|
|
+ StringUtils.normalize(subcategory.label),
|
|
|
+ normalizedSearch,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ return true
|
|
|
|
|
|
// Trouver et vérifier le label de la catégorie parente
|
|
|
if (subcategory && subcategory.parentId) {
|
|
|
- const category = props.items.find(i => i.id === subcategory.parentId)
|
|
|
- if (category && anyWordStartsWith(StringUtils.normalize(category.label), normalizedSearch)) return true
|
|
|
+ const category = props.items.find((i) => i.id === subcategory.parentId)
|
|
|
+ if (
|
|
|
+ category &&
|
|
|
+ anyWordStartsWith(
|
|
|
+ StringUtils.normalize(category.label),
|
|
|
+ normalizedSearch,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ return true
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
@@ -340,10 +383,9 @@ const itemMatchesSearch = (item: SelectItem): boolean => {
|
|
|
* @returns {SelectItem[]} Les éléments de niveau 2 qui correspondent à la recherche.
|
|
|
*/
|
|
|
const findMatchingLevel2Items = (): SelectItem[] => {
|
|
|
- return props.items.filter(item =>
|
|
|
- item.type === 'item' &&
|
|
|
- item.level === 2 &&
|
|
|
- itemMatchesSearch(item)
|
|
|
+ return props.items.filter(
|
|
|
+ (item) =>
|
|
|
+ item.type === 'item' && item.level === 2 && itemMatchesSearch(item),
|
|
|
)
|
|
|
}
|
|
|
|
|
|
@@ -361,11 +403,11 @@ const buildSearchResultsList = (matchingItems: SelectItem[]): SelectItem[] => {
|
|
|
|
|
|
for (const item of matchingItems) {
|
|
|
// Trouver la sous-catégorie parente
|
|
|
- const subcategory = props.items.find(i => i.id === item.parentId)
|
|
|
+ const subcategory = props.items.find((i) => i.id === item.parentId)
|
|
|
if (!subcategory) continue
|
|
|
|
|
|
// Trouver la catégorie parente
|
|
|
- const category = props.items.find(i => i.id === subcategory.parentId)
|
|
|
+ const category = props.items.find((i) => i.id === subcategory.parentId)
|
|
|
if (!category) continue
|
|
|
|
|
|
// Ajouter la catégorie si elle n'est pas déjà présente
|
|
|
@@ -400,14 +442,14 @@ const buildSearchResultsList = (matchingItems: SelectItem[]): SelectItem[] => {
|
|
|
const processItemsRecursively = (
|
|
|
items: SelectItem[],
|
|
|
result: SelectItem[],
|
|
|
- parentExpanded = true
|
|
|
+ parentExpanded = true,
|
|
|
): void => {
|
|
|
for (const item of items) {
|
|
|
if (item.type === 'category') {
|
|
|
result.push(item)
|
|
|
if (expandedCategories.value.has(item.id)) {
|
|
|
- const subcategories = props.items.filter(i =>
|
|
|
- i.parentId === item.id && i.type === 'subcategory'
|
|
|
+ const subcategories = props.items.filter(
|
|
|
+ (i) => i.parentId === item.id && i.type === 'subcategory',
|
|
|
)
|
|
|
processItemsRecursively(subcategories, result, true)
|
|
|
}
|
|
|
@@ -415,8 +457,8 @@ const processItemsRecursively = (
|
|
|
if (parentExpanded) {
|
|
|
result.push(item)
|
|
|
if (expandedSubcategories.value.has(item.id)) {
|
|
|
- const subItems = props.items.filter(i =>
|
|
|
- i.parentId === item.id && i.type === 'item'
|
|
|
+ const subItems = props.items.filter(
|
|
|
+ (i) => i.parentId === item.id && i.type === 'item',
|
|
|
)
|
|
|
processItemsRecursively(subItems, result, true)
|
|
|
}
|
|
|
@@ -434,7 +476,7 @@ const processItemsRecursively = (
|
|
|
*/
|
|
|
const buildNormalModeList = (): SelectItem[] => {
|
|
|
const result: SelectItem[] = []
|
|
|
- const topLevelItems = props.items.filter(item => !item.parentId)
|
|
|
+ const topLevelItems = props.items.filter((item) => !item.parentId)
|
|
|
processItemsRecursively(topLevelItems, result)
|
|
|
return result
|
|
|
}
|
|
|
@@ -467,10 +509,12 @@ const selectedItemsMap = computed(() => {
|
|
|
const map: Record<string, string> = {}
|
|
|
|
|
|
// Find all selectable items (type 'item') in the original items array
|
|
|
- const selectableItems = props.items.filter(item => item.type === 'item' && item.value)
|
|
|
+ const selectableItems = props.items.filter(
|
|
|
+ (item) => item.type === 'item' && item.value,
|
|
|
+ )
|
|
|
|
|
|
// Create a map of values to labels
|
|
|
- selectableItems.forEach(item => {
|
|
|
+ selectableItems.forEach((item) => {
|
|
|
if (item.value) {
|
|
|
map[item.value] = item.label
|
|
|
}
|