Commit a84656fc authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'filtered-search-keyboard-navigation' into 'master'

Fixed keyboard navigation not working in filtered search bar

Closes #26840

See merge request !8656
parents 1e2a9604 203db3cf
......@@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill');
var utils = require('./utils');
var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true;
this.list = list;
this.items = [];
......@@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, {
},
show: function() {
// debugger
this.list.style.display = 'block';
this.hidden = false;
if (this.hidden) {
// debugger
this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
}
},
hide: function() {
// debugger
this.list.style.display = 'none';
this.hidden = true;
if (!this.hidden) {
// debugger
this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
}
},
destroy: function() {
......@@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, {
this.input = function input(e) {
if(self.hasRemovedEvents) return;
self.list.show();
var inputEvent = new CustomEvent('input.dl', {
detail: {
hook: self,
......@@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, {
cancelable: true
});
e.target.dispatchEvent(inputEvent);
self.list.show();
}
this.keyup = function keyup(e) {
......@@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, {
}
function keyEvent(e, keyEventName){
self.list.show();
var keyEvent = new CustomEvent(keyEventName, {
detail: {
hook: self,
......@@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, {
cancelable: true
});
e.target.dispatchEvent(keyEvent);
self.list.show();
}
this.events = this.events || {};
......@@ -572,24 +581,43 @@ require('./window')(function(w){
module.exports = function(){
var currentKey;
var currentFocus;
var currentIndex = 0;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
var listItems = list.list.querySelectorAll('li');
var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
var listItemsTmp = [];
for(var i = 0; i < listItems.length; i++) {
listItems[i].classList.remove('dropdown-active');
var listItem = listItems[i];
listItem.classList.remove('dropdown-active');
if (listItem.style.display !== 'none') {
listItemsTmp.push(listItem);
}
}
return listItems;
return listItemsTmp;
};
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
if(currentIndex>0){
if(!listItems[currentIndex-1]){
currentIndex = currentIndex-1;
if(list.currentIndex>0){
if(!listItems[list.currentIndex-1]){
list.currentIndex = list.currentIndex-1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add('dropdown-active');
if (filterDropdownEl) {
var filterDropdownBottom = filterDropdownEl.offsetHeight;
var elOffsetTop = el.offsetTop - 30;
if (elOffsetTop > filterDropdownBottom) {
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
}
}
}
listItems[currentIndex-1].classList.add('dropdown-active');
}
};
......@@ -597,13 +625,13 @@ require('./window')(function(w){
var list = e.detail.hook.list;
removeHighlight(list);
list.show();
currentIndex = 0;
list.currentIndex = 0;
isUpArrow = false;
isDownArrow = false;
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
var currentItem = listItems[currentIndex-1];
var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
......@@ -617,6 +645,8 @@ require('./window')(function(w){
var keydown = function keydown(e){
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
......@@ -648,6 +678,7 @@ require('./window')(function(w){
if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
......
......@@ -93,6 +93,7 @@ require('../window')(function(w){
self.hook.list.setData.call(self.hook.list, data);
}
self.notLoading();
self.hook.list.currentIndex = 0;
});
},
......@@ -142,4 +143,4 @@ module.exports = function(callback) {
};
},{}]},{},[1])(1)
});
\ No newline at end of file
});
......@@ -6,6 +6,8 @@ require('../window')(function(w){
w.droplabFilter = {
keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
......@@ -27,10 +29,22 @@ require('../window')(function(w){
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
list.render(matches);
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches);
list.currentIndex = 0;
}
},
init: function init(hookInput) {
......@@ -57,4 +71,4 @@ module.exports = function(callback) {
};
},{}]},{},[1])(1)
});
\ No newline at end of file
});
......@@ -84,7 +84,7 @@
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_'));
inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
......
......@@ -64,13 +64,26 @@
}
checkForEnter(e) {
if (e.keyCode === 38 || e.keyCode === 40) {
const selectionStart = this.filteredSearchInput.selectionStart;
e.preventDefault();
this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
}
if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
e.preventDefault();
// Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
if (!activeElements.length) {
// Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
this.search();
this.search();
}
}
}
......
......@@ -79,6 +79,16 @@
overflow: auto;
}
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
text-decoration: none;
.avatar {
border-color: $white-light;
}
}
.filter-dropdown-item {
.btn {
border: none;
......@@ -103,13 +113,7 @@
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
text-decoration: none;
.avatar {
border-color: $white-light;
}
@extend %filter-dropdown-item-btn-hover;
}
}
......@@ -131,6 +135,12 @@
}
}
.filter-dropdown-item.dropdown-active {
.btn {
@extend %filter-dropdown-item-btn-hover;
}
}
.hint-dropdown {
width: 250px;
}
......
......@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
describe 'keyboard navigation' do
it 'selects label' do
send_keys_to_filtered_search('label:')
filtered_search.native.send_keys(:down, :down, :enter)
expect(filtered_search.value).to eq("label:~#{special_label.name}")
end
end
describe 'behavior' do
it 'opens when the search bar has label:' do
filtered_search.set('label:')
......
......@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do
left_style.to_s.gsub('left: ', '').to_f
end
describe 'keyboard navigation' do
it 'makes item active' do
filtered_search.native.send_keys(:down)
page.within '#js-dropdown-hint' do
expect(page).to have_selector('.dropdown-active')
end
end
it 'selects item' do
filtered_search.native.send_keys(:down, :down, :enter)
expect(filtered_search.value).to eq('author:')
end
end
describe 'clear search button' do
it 'clears text' do
search_text = 'search_text'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment