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'); ...@@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill');
var utils = require('./utils'); var utils = require('./utils');
var DropDown = function(list) { var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true; this.hidden = true;
this.list = list; this.list = list;
this.items = []; this.items = [];
...@@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, { ...@@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, {
}, },
show: function() { show: function() {
if (this.hidden) {
// debugger // debugger
this.list.style.display = 'block'; this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false; this.hidden = false;
}
}, },
hide: function() { hide: function() {
if (!this.hidden) {
// debugger // debugger
this.list.style.display = 'none'; this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true; this.hidden = true;
}
}, },
destroy: function() { destroy: function() {
...@@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, { ...@@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, {
this.input = function input(e) { this.input = function input(e) {
if(self.hasRemovedEvents) return; if(self.hasRemovedEvents) return;
self.list.show();
var inputEvent = new CustomEvent('input.dl', { var inputEvent = new CustomEvent('input.dl', {
detail: { detail: {
hook: self, hook: self,
...@@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, { ...@@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, {
cancelable: true cancelable: true
}); });
e.target.dispatchEvent(inputEvent); e.target.dispatchEvent(inputEvent);
self.list.show();
} }
this.keyup = function keyup(e) { this.keyup = function keyup(e) {
...@@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, { ...@@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, {
} }
function keyEvent(e, keyEventName){ function keyEvent(e, keyEventName){
self.list.show();
var keyEvent = new CustomEvent(keyEventName, { var keyEvent = new CustomEvent(keyEventName, {
detail: { detail: {
hook: self, hook: self,
...@@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, { ...@@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, {
cancelable: true cancelable: true
}); });
e.target.dispatchEvent(keyEvent); e.target.dispatchEvent(keyEvent);
self.list.show();
} }
this.events = this.events || {}; this.events = this.events || {};
...@@ -572,24 +581,43 @@ require('./window')(function(w){ ...@@ -572,24 +581,43 @@ require('./window')(function(w){
module.exports = function(){ module.exports = function(){
var currentKey; var currentKey;
var currentFocus; var currentFocus;
var currentIndex = 0;
var isUpArrow = false; var isUpArrow = false;
var isDownArrow = false; var isDownArrow = false;
var removeHighlight = function removeHighlight(list) { 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++) { 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 setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
if(currentIndex>0){ if(list.currentIndex>0){
if(!listItems[currentIndex-1]){ if(!listItems[list.currentIndex-1]){
currentIndex = 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){ ...@@ -597,13 +625,13 @@ require('./window')(function(w){
var list = e.detail.hook.list; var list = e.detail.hook.list;
removeHighlight(list); removeHighlight(list);
list.show(); list.show();
currentIndex = 0; list.currentIndex = 0;
isUpArrow = false; isUpArrow = false;
isDownArrow = false; isDownArrow = false;
}; };
var selectItem = function selectItem(list) { var selectItem = function selectItem(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
var currentItem = listItems[currentIndex-1]; var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', { var listEvent = new CustomEvent('click.dl', {
detail: { detail: {
list: list, list: list,
...@@ -617,6 +645,8 @@ require('./window')(function(w){ ...@@ -617,6 +645,8 @@ require('./window')(function(w){
var keydown = function keydown(e){ var keydown = function keydown(e){
var typedOn = e.target; var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false; isUpArrow = false;
isDownArrow = false; isDownArrow = false;
...@@ -648,6 +678,7 @@ require('./window')(function(w){ ...@@ -648,6 +678,7 @@ require('./window')(function(w){
if(isUpArrow){ currentIndex--; } if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; } if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; } if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list); setMenuForArrows(e.detail.hook.list);
}; };
......
...@@ -93,6 +93,7 @@ require('../window')(function(w){ ...@@ -93,6 +93,7 @@ require('../window')(function(w){
self.hook.list.setData.call(self.hook.list, data); self.hook.list.setData.call(self.hook.list, data);
} }
self.notLoading(); self.notLoading();
self.hook.list.currentIndex = 0;
}); });
}, },
......
...@@ -6,6 +6,8 @@ require('../window')(function(w){ ...@@ -6,6 +6,8 @@ require('../window')(function(w){
w.droplabFilter = { w.droplabFilter = {
keydownWrapper: function(e){ keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list; var list = e.detail.hook.list;
var data = list.data; var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase(); var value = e.detail.hook.trigger.value.toLowerCase();
...@@ -27,10 +29,22 @@ require('../window')(function(w){ ...@@ -27,10 +29,22 @@ require('../window')(function(w){
}; };
} }
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) { matches = data.map(function(o) {
return filterFunction(o, value); return filterFunction(o, value);
}); });
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches); list.render(matches);
list.currentIndex = 0;
}
}, },
init: function init(hookInput) { init: function init(hookInput) {
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
let inputValue = input.value; let inputValue = input.value;
// Replace all spaces inside quote marks with underscores // Replace all spaces inside quote marks with underscores
// This helps with matching the beginning & end of a token:key // 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 // Get the right position for the word selected
// Regex matches first space // Regex matches first space
......
...@@ -64,15 +64,28 @@ ...@@ -64,15 +64,28 @@
} }
checkForEnter(e) { 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) { if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
e.preventDefault(); e.preventDefault();
if (!activeElements.length) {
// Prevent droplab from opening dropdown // Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab(); this.dropdownManager.destroyDroplab();
this.search(); this.search();
} }
} }
}
toggleClearSearchButton(e) { toggleClearSearchButton(e) {
if (e.target.value) { if (e.target.value) {
......
...@@ -79,6 +79,16 @@ ...@@ -79,6 +79,16 @@
overflow: auto; 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 { .filter-dropdown-item {
.btn { .btn {
border: none; border: none;
...@@ -103,13 +113,7 @@ ...@@ -103,13 +113,7 @@
&:hover, &:hover,
&:focus { &:focus {
background-color: $dropdown-hover-color; @extend %filter-dropdown-item-btn-hover;
color: $white-light;
text-decoration: none;
.avatar {
border-color: $white-light;
}
} }
} }
...@@ -131,6 +135,12 @@ ...@@ -131,6 +135,12 @@
} }
} }
.filter-dropdown-item.dropdown-active {
.btn {
@extend %filter-dropdown-item-btn-hover;
}
}
.hint-dropdown { .hint-dropdown {
width: 250px; width: 250px;
} }
......
...@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
end 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 describe 'behavior' do
it 'opens when the search bar has label:' do it 'opens when the search bar has label:' do
filtered_search.set('label:') filtered_search.set('label:')
......
...@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do ...@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do
left_style.to_s.gsub('left: ', '').to_f left_style.to_s.gsub('left: ', '').to_f
end 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 describe 'clear search button' do
it 'clears text' do it 'clears text' do
search_text = 'search_text' 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