Files
matosbox/frontend/components/TreeList.vue

95 lines
1.9 KiB
Vue

<template>
<ul class="tree-list">
<li v-for="node in flatNodes" :key="node.id" :style="{ paddingLeft: `${node.depth * 12}px` }">
<button
v-if="node.hasChildren"
class="card"
type="button"
style="margin-right: 6px;"
@click="toggle(node.id)"
>
{{ isCollapsed(node.id) ? '+' : '-' }}
</button>
<span>{{ node.nom }}</span>
</li>
</ul>
</template>
<script setup lang="ts">
type TreeItem = {
id: string
nom: string
parent_id?: string | null
}
type FlatNode = TreeItem & {
depth: number
hasChildren: boolean
}
const props = defineProps<{ items: TreeItem[] }>()
const collapsed = ref<Set<string>>(new Set())
const childrenMap = computed(() => {
const map = new Map<string | null, TreeItem[]>()
props.items.forEach((item) => {
const key = item.parent_id || null
const list = map.get(key) || []
list.push(item)
map.set(key, list)
})
return map
})
const hasChildren = (id: string) => {
const list = childrenMap.value.get(id)
return !!(list && list.length)
}
const buildFlat = (parentId: string | null, depth: number, acc: FlatNode[]) => {
const children = childrenMap.value.get(parentId) || []
children
.slice()
.sort((a, b) => a.nom.localeCompare(b.nom))
.forEach((child) => {
acc.push({
...child,
depth,
hasChildren: hasChildren(child.id)
})
if (!collapsed.value.has(child.id)) {
buildFlat(child.id, depth + 1, acc)
}
})
}
const flatNodes = computed(() => {
const acc: FlatNode[] = []
buildFlat(null, 0, acc)
return acc
})
const toggle = (id: string) => {
const next = new Set(collapsed.value)
if (next.has(id)) {
next.delete(id)
} else {
next.add(id)
}
collapsed.value = next
}
const isCollapsed = (id: string) => collapsed.value.has(id)
</script>
<style scoped>
.tree-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 6px;
}
</style>