Commit 451b1c3b authored by Heinrich Lee Yu's avatar Heinrich Lee Yu Committed by Fatih Acet

Fix keyboard shortcuts in header search bar

Fixes a bug where hidden.bs.dropdown was manually triggered
but the dropdown was not actually hidden. Triggering the event
caused the event handlers to be unloaded. The events are supposed
to be attached again when the dropdown is opened but in this case
the dropdown is still open.
parent 14227cac
...@@ -617,7 +617,7 @@ GitLabDropdown = (function() { ...@@ -617,7 +617,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.hidden = function(e) { GitLabDropdown.prototype.hidden = function(e) {
var $input; var $input;
this.resetRows(); this.resetRows();
this.removeArrayKeyEvent(); this.removeArrowKeyEvent();
$input = this.dropdown.find('.dropdown-input-field'); $input = this.dropdown.find('.dropdown-input-field');
if (this.options.filterable) { if (this.options.filterable) {
$input.blur(); $input.blur();
...@@ -900,7 +900,7 @@ GitLabDropdown = (function() { ...@@ -900,7 +900,7 @@ GitLabDropdown = (function() {
); );
}; };
GitLabDropdown.prototype.removeArrayKeyEvent = function() { GitLabDropdown.prototype.removeArrowKeyEvent = function() {
return $('body').off('keydown'); return $('body').off('keydown');
}; };
......
/* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, no-lonely-if, vars-on-top */ /* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, vars-on-top */
import $ from 'jquery'; import $ from 'jquery';
import { escape, throttle } from 'underscore'; import { escape, throttle } from 'underscore';
...@@ -95,7 +95,6 @@ export class SearchAutocomplete { ...@@ -95,7 +95,6 @@ export class SearchAutocomplete {
this.createAutocomplete(); this.createAutocomplete();
} }
this.saveTextLength();
this.bindEvents(); this.bindEvents();
this.dropdownToggle.dropdown(); this.dropdownToggle.dropdown();
this.searchInput.addClass('js-autocomplete-disabled'); this.searchInput.addClass('js-autocomplete-disabled');
...@@ -107,7 +106,7 @@ export class SearchAutocomplete { ...@@ -107,7 +106,7 @@ export class SearchAutocomplete {
this.onClearInputClick = this.onClearInputClick.bind(this); this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this); this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.setScrollFade = this.setScrollFade.bind(this); this.setScrollFade = this.setScrollFade.bind(this);
} }
getElement(selector) { getElement(selector) {
...@@ -118,10 +117,6 @@ export class SearchAutocomplete { ...@@ -118,10 +117,6 @@ export class SearchAutocomplete {
return (this.originalState = this.serializeState()); return (this.originalState = this.serializeState());
} }
saveTextLength() {
return (this.lastTextLength = this.searchInput.val().length);
}
createAutocomplete() { createAutocomplete() {
return this.searchInput.glDropdown({ return this.searchInput.glDropdown({
filterInputBlur: false, filterInputBlur: false,
...@@ -318,12 +313,16 @@ export class SearchAutocomplete { ...@@ -318,12 +313,16 @@ export class SearchAutocomplete {
} }
bindEvents() { bindEvents() {
this.searchInput.on('keydown', this.onSearchInputKeyDown); this.searchInput.on('input', this.onSearchInputChange);
this.searchInput.on('keyup', this.onSearchInputKeyUp); this.searchInput.on('keyup', this.onSearchInputKeyUp);
this.searchInput.on('focus', this.onSearchInputFocus); this.searchInput.on('focus', this.onSearchInputFocus);
this.searchInput.on('blur', this.onSearchInputBlur); this.searchInput.on('blur', this.onSearchInputBlur);
this.clearInput.on('click', this.onClearInputClick); this.clearInput.on('click', this.onClearInputClick);
this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250)); this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250));
this.searchInput.on('click', e => {
e.stopPropagation();
});
} }
enableAutocomplete() { enableAutocomplete() {
...@@ -342,43 +341,19 @@ export class SearchAutocomplete { ...@@ -342,43 +341,19 @@ export class SearchAutocomplete {
} }
} }
// Saves last length of the entered text onSearchInputChange() {
onSearchInputKeyDown() { this.enableAutocomplete();
return this.saveTextLength();
} }
onSearchInputKeyUp(e) { onSearchInputKeyUp(e) {
switch (e.keyCode) { switch (e.keyCode) {
case KEYCODE.BACKSPACE:
// When removing the last character and no badge is present
if (this.lastTextLength === 1) {
this.disableAutocomplete();
}
// When removing any character from existin value
if (this.lastTextLength > 1) {
this.enableAutocomplete();
}
break;
case KEYCODE.ESCAPE: case KEYCODE.ESCAPE:
this.restoreOriginalState(); this.restoreOriginalState();
break; break;
case KEYCODE.ENTER: case KEYCODE.ENTER:
this.disableAutocomplete(); this.disableAutocomplete();
break; break;
case KEYCODE.UP:
case KEYCODE.DOWN:
return;
default: default:
// Handle the case when deleting the input value other than backspace
// e.g. Pressing ctrl + backspace or ctrl + x
if (this.searchInput.val() === '') {
this.disableAutocomplete();
} else {
// We should display the menu only when input is not empty
if (e.keyCode !== KEYCODE.ENTER) {
this.enableAutocomplete();
}
}
} }
this.wrap.toggleClass('has-value', Boolean(e.target.value)); this.wrap.toggleClass('has-value', Boolean(e.target.value));
} }
...@@ -434,7 +409,7 @@ export class SearchAutocomplete { ...@@ -434,7 +409,7 @@ export class SearchAutocomplete {
disableAutocomplete() { disableAutocomplete() {
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) { if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
this.searchInput.addClass('js-autocomplete-disabled'); this.searchInput.addClass('js-autocomplete-disabled');
this.dropdown.removeClass('show').trigger('hidden.bs.dropdown'); this.dropdown.dropdown('toggle');
this.restoreMenu(); this.restoreMenu();
} }
} }
......
---
title: Fix keyboard shortcuts in header search autocomplete
merge_request: 18685
author:
type: fixed
...@@ -26,6 +26,16 @@ describe 'User uses header search field', :js do ...@@ -26,6 +26,16 @@ describe 'User uses header search field', :js do
end end
end end
context 'when using the keyboard shortcut' do
before do
find('body').native.send_keys('s')
end
it 'shows the category search dropdown' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
context 'when clicking the search field' do context 'when clicking the search field' do
before do before do
page.find('#search.js-autocomplete-disabled').click page.find('#search.js-autocomplete-disabled').click
...@@ -77,15 +87,21 @@ describe 'User uses header search field', :js do ...@@ -77,15 +87,21 @@ describe 'User uses header search field', :js do
end end
context 'when entering text into the search field' do context 'when entering text into the search field' do
before do it 'does not display the category search dropdown' do
page.within('.search-input-wrap') do page.within('.search-input-wrap') do
fill_in('search', with: scope_name.first(4)) fill_in('search', with: scope_name.first(4))
end end
end
it 'does not display the category search dropdown' do
expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i) expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end end
it 'hides the dropdown when there are no results' do
page.within('.search-input-wrap') do
fill_in('search', with: 'a_search_term_with_no_results')
end
expect(page).not_to have_selector('.dropdown-menu')
end
end end
end end
......
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