У меня есть панель поиска, где пользователи могут фильтровать список продуктов и перемещаться по этому списку с помощью стрелок вверх и вниз. Фильтрация и навигация работают нормально, но есть побочный эффект использования стрелок, который мне не удалось подавить. : их использование приводит к прокрутке всей страницы (если она достаточно велика, чтобы иметь полосу прокрутки), а если на странице нет полосы прокрутки, список также не будет прокручиваться должным образом (как показано на втором и третьем изображениях).
Моя строка поиска выглядит так:
На двух изображениях ниже вы можете видеть, что при использовании стрелки вниз элемент частично скрыт; и при использовании сначала стрелки вверх (список начинает перемещаться снизу вверх, затем элемент полностью скрывается с экрана.
Мой файл .vue
выглядит так:
<template>
<form class = "ui-search-bar" role = "search" aria-label = "Produtos à venda">
<input
aria-label = "Pesquisar por produto"
class = "ui-input"
@focus = "enableArrowNavigation"
placeholder = "Pesquisar"
type = "text"
v-model = "search"
@keydown.up = "navigateUp"
@keydown.down = "navigateDown"
/>
<div class = "ui-search-bar__wrapper">
<div>
<ul v-if = "copied_parents.length > 0">
<li class = "ui-search-bar__grandpa" :key = "i" v-for = "(parent, i) in copied_parents">
<p>{{ parent.title }}</p>
<ul class = "ui-search-bar__parent" :key = "j" v-for = "(child, j) in parent[children_key]">
<li
class = "ui-search-bar__children"
:data-category = "child.title"
:key = "k"
v-for = "(grandchild, k) in child[grandchildren_key]"
>
<a
:href = "grandchild.route"
@keydown.up = "navigateUp"
@keydown.down = "navigateDown"
tabindex = "-1"
v-if = "grandchild.quantity > 0"
>
{{ grandchild.title }}
</a>
<p v-else>
{{ grandchild.title }} -
<span> Esgotado </span>
</p>
</li>
</ul>
</li>
</ul>
<p v-else-if = "parents.length <= 0">Não há produtos disponíveis para compra.</p>
<p v-else>Não há resultados para esta pesquisa.</p>
</div>
</div>
</form>
</template>
<script>
export default {
props: ["children_key", "grandchildren_key", "parents"],
data() {
return {
amount_of_children: 0,
copied_parents: this.makeParentCopy(),
search: "",
selected_child: 0,
};
},
watch: {
search(value) {
if (value.length >= 3) {
this.copied_parents = this.filterItems(value);
} else {
this.copied_parents = this.makeParentCopy();
}
},
},
mounted() {
this.amount_of_children = document.querySelectorAll(".ui-search-bar__children a").length;
},
methods: {
makeParentCopy() {
return JSON.parse(JSON.stringify(this.parents));
},
filterItems(needle) {
this.copied_parents = this.makeParentCopy();
const filtered_items = this.copied_parents.filter((parent) => {
const children = parent[this.children_key].filter((child) => {
const grandchildren = child[this.grandchildren_key].filter((product) => {
return product.title.toLowerCase().includes(needle.toLowerCase());
});
child[this.grandchildren_key] = grandchildren;
if (grandchildren.length > 0) {
return child;
}
});
parent[this.children_key] = children;
if (children.length > 0) {
return children;
}
});
return filtered_items;
},
enableArrowNavigation() {
this.selected_child = 0;
},
navigate() {
document.querySelectorAll(".ui-search-bar__children a")[this.selected_child - 1]?.focus();
},
navigateDown() {
if (this.selected_child === this.amount_of_children) {
this.selected_child = 1;
} else {
this.selected_child++;
}
this.navigate();
},
navigateUp() {
if (this.selected_child > 1) {
this.selected_child--;
} else if (this.selected_child === 1) {
this.selected_child = this.amount_of_children;
}
this.navigate();
},
},
};
</script>
А .sass
выглядит так:
@use "../abstracts/colours" as c;
@use "../abstracts/shorthands" as s;
@use "../abstracts/media" as m;
.ui-search-bar {
padding: 10px 10px 0;
position: relative;
width: 70%;
z-index: 100;
& input {
position: relative;
width: 100%;
z-index: 110;
}
&:focus-within > .ui-search-bar__wrapper {
display: flex;
flex-direction: column;
}
&__children {
position: relative;
width: 100%;
& a,
& p {
border-radius: 6px;
color: c.$darker-gray;
display: inline-block;
@include s.font(14);
padding: 1px 5px;
text-decoration: none;
width: 100%;
}
& p {
color: c.$dark-gray;
& > span {
color: c.$red;
@include s.font(16);
font-variant-caps: all-petite-caps;
}
}
a:focus,
a:hover {
@include m.desktop-up {
background-color: c.$gray;
outline-color: c.$gray;
outline-style: solid;
outline-width: 2px;
}
}
&:first-of-type::before {
color: c.$dark-gray;
content: attr(data-category);
@include s.font(14);
position: absolute;
right: 0;
top: 0;
}
}
&__grandpa {
& > p {
color: c.$lead;
@include s.font(18);
}
&:not(:first-of-type) {
margin-top: 15px;
}
&:last-of-type() {
margin-bottom: 5px;
}
}
&__parent {
border-top: 1px solid c.$gray;
padding: 5px 0;
}
&__wrapper {
background-color: white;
border-radius: 8px;
box-shadow: 0px 0px 12px -5px #1d283a;
display: none;
left: 0;
max-height: 500px;
padding-top: 60px;
position: absolute;
top: 0;
width: 100%;
& > div {
overflow-y: auto;
padding-inline: 10px;
width: 100%;
}
& > div > ul {
width: 100%;
}
& > div > p {
color: c.$dark-gray;
@include s.font(18, 500);
padding: 10px 20px;
text-align: center;
}
}
}
Я действительно не нашел ничего, что могло бы мне помочь в этом. Я думаю, это потому, что я действительно не знаю, является ли это проблемой css
или js
, и поэтому я пришел к вам: может быть, у кого-то есть подсказка, что может быть причиной этого, и, надеюсь, решение тоже.
Вам нужно предотвратить поведение браузера по умолчанию, вызвав event.preventDefault()
.
В Vue.js вы можете сделать это прямо в шаблоне следующим образом:
<input
...
@keydown.up.prevent = "navigateUp"
@keydown.down.prevent = "navigateDown"
/>
Да, порядок модификаторов имеет значение, потому что код генерируется именно в таком порядке, вы можете узнать больше об этом в документации.
Большое спасибо. На самом деле я пробовал это раньше, но вместо того, чтобы использовать
prevent
послеup
илиdown
, я использовал его раньше.