Reputation: 103
I have a serach bar where users can filter a list of products and navigate through this list using arrows up and down.The filtering and navigation work ok, but there is a side effect of using the arrows that I haven't been able to surpress: using them makes all the page scroll (if it's large enough to have a scrollbar), and if the page hasn't got the scrollbar, the list won't scroll properly either (as shown on second and third images).
My search bar looks like this:
In the two images below you can see that when using arrow down first, the element is partially hidden; and when using the arrow up first (the list starts being navigated bottom to top, then the element is hidden entirelly from the screen.
My .vue
file looks like this:
<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>
And the .sass
looks like this:
@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;
}
}
}
I haven't really found anything that could help me with this. I guess it's because I don't really know if this is a css
or js
problem, and that's why I come to you: maybe someone will have a hint what could be causing this and hopefully a solution too.
Upvotes: 0
Views: 1038
Reputation: 175
You need to prevent the default browser behavior by calling event.preventDefault()
.
In Vue.js you can do this directly in the template like so:
<input
...
@keydown.up.prevent="navigateUp"
@keydown.down.prevent="navigateDown"
/>
Upvotes: 1