Ваш фильтр правильно находит все элементы с соответствующим заголовком в своем массиве подэлементов, но он не отфильтровывает несоответствующие подэлементы. Для этого просто добавьте еще одну строку для фильтрации подпунктов:
filteredPosts() {
let compItems = this.compItems;
if (this.filterTitle) {
// find all items with a matching title in its `items[]`
compItems = compItems.filter(item => ...);
// filter out non-matching `items[]`
compItems = compItems.map(item => {
const items = item.items.filter(x => x.title.toLowerCase().indexOf(this.filterTitle.toLowerCase()) !== -1)
return {
...item,
items // overwrite `items[]` with our filtered one
}
})
}
return compItems;
}
new Vue({
el: '#app',
data() {
return {
filterTitle: '',
compItems: []
}
},
mounted() {
this.compItems = [
{
"title": "b",
"link": "b",
"items": [
{
"title": "BBB",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
{
"title": "XXX",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
{
"title": "BBB 2",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
]
},
{
"title": "h",
"link": "h",
"items": [
{
"title": "HHH",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
{
"title": "XXX",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
{
"title": "BBB 3",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur libero ipsum, euismod posuere posuere ac, rhoncus eget nisi. Praesent ac lorem ut est fringilla porta.</p>",
"active": false
},
]
}
]
},
computed: {
filteredPosts() {
let compItems = this.compItems;
if (this.filterTitle) {
compItems = compItems.filter(item => {
return item.items.some(subItem => {
return (
subItem.title
.toLowerCase()
.indexOf(this.filterTitle.toLowerCase()) !== -1
);
});
});
compItems = compItems.map(item => {
const items = item.items.filter(x => x.title.toLowerCase().indexOf(this.filterTitle.toLowerCase()) !== -1)
return {
...item,
items
}
})
}
return compItems;
}
}
})
<script src="https://unpkg.com/vue@2.6.11/dist/vue.min.js"></script>
<div id="app">
<input
placeholder="Filter by Name"
v-model="filterTitle"
/>
<div v-for="(item, index) in filteredPosts" :key="index">
{{ item.title }}
<ul v-for="(subItem, index) in item.items" :key="index">
<li>{{ subItem.title }}</li>
</ul>
<hr />
</div>
</div>