Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropdown: refine handling of keydown in input and textarea #21802

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions js/src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,17 @@ const Dropdown = (() => {
}

static _dataApiKeydownHandler(event) {
if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE ||
/input|textarea/i.test(event.target.tagName)) {
// If not input/textarea:
// - And not a key in REGEXP_KEYDOWN => not a dropdown command
// If input/textarea:
// - If space key => not a dropdown command
// - If key is other than excape
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escape

// - If key is not up or down => not a dropdown command
// - If trigger inside the menu => not a dropdown command
if (/input|textarea/i.test(event.target.tagName) ?
event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE &&
(event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE ||
$(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {
return
}

Expand Down
146 changes: 141 additions & 5 deletions js/tests/unit/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ $(function () {
$dropdown.trigger('click')
})

QUnit.test('should ignore keyboard events within <input>s and <textarea>s', function (assert) {
assert.expect(3)
QUnit.test('should ignore keyboard events for <input>s and <textarea>s within dropdown-menu, except for escape key', function (assert) {
assert.expect(8)
var done = assert.async()

var dropdownHTML = '<div class="tabs">'
Expand Down Expand Up @@ -487,18 +487,154 @@ $(function () {
.on('shown.bs.dropdown', function () {
assert.ok(true, 'shown was fired')

$input.trigger('focus').trigger($.Event('keydown', { which: 38 }))
assert.ok($(document.activeElement).is($input), 'input still focused')
// Space key
$input.trigger('focus').trigger($.Event('keydown', { which: 32 }))
assert.ok($(document.activeElement)[0] === $input[0], 'input still focused')
$textarea.trigger('focus').trigger($.Event('keydown', { which: 32 }))
assert.ok($(document.activeElement)[0] === $textarea[0], 'textarea still focused')

// Key up
$input.trigger('focus').trigger($.Event('keydown', { which: 38 }))
assert.ok($(document.activeElement)[0] === $input[0], 'input still focused')
$textarea.trigger('focus').trigger($.Event('keydown', { which: 38 }))
assert.ok($(document.activeElement).is($textarea), 'textarea still focused')
assert.ok($(document.activeElement)[0] === $textarea[0], 'textarea still focused')

// Key down
$input.trigger('focus').trigger($.Event('keydown', { which: 40 }))
assert.ok($(document.activeElement)[0] === $input[0], 'input still focused')
$textarea.trigger('focus').trigger($.Event('keydown', { which: 40 }))
assert.ok($(document.activeElement)[0] === $textarea[0], 'textarea still focused')

// Key escape
$input.trigger('focus').trigger($.Event('keydown', { which: 27 }))
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is not shown')

done()
})

$dropdown.trigger('click')
})

QUnit.test('should ignore space key events for <input>s within dropdown, and accept up, down and escape', function (assert) {
assert.expect(6)
var done = assert.async()

var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<input type="text" id="input" data-toggle="dropdown">'
+ '<ul class="dropdown-menu" role="menu">'
+ '<li class="dropdown-item"><a id="item1" href="#">Secondary link</a></li>'
+ '<li class="dropdown-item"><a id="item2" href="#">Something else here</a></li>'
+ '<li class="divider"/>'
+ '<li class="dropdown-item"><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()

var $input = $('#input')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {
assert.ok(true, 'shown was fired')

// Key space
$input.trigger('focus').trigger($.Event('keydown', { which: 32 }))
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
assert.ok($(document.activeElement).is($input), 'input is still focused')

// Key escape
$input.trigger('focus').trigger($.Event('keydown', { which: 27 }))
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is not shown')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {

// Key down
$input.trigger('focus').trigger($.Event('keydown', { which: 40 }))
assert.ok(document.activeElement === $('#item1')[0], 'item1 is focused')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {

// Key up
$input.trigger('focus').trigger($.Event('keydown', { which: 38 }))
assert.ok(document.activeElement === $('#item1')[0], 'item1 is focused')
done()
}).bootstrapDropdown('toggle')
$input.trigger('click')
})
$input.trigger('click')
})
$input.trigger('click')
})

QUnit.test('should ignore space key events for <textarea>s within dropdown, and accept up, down and escape', function (assert) {
assert.expect(6)
var done = assert.async()

var dropdownHTML = '<ul class="tabs">'
+ '<li class="dropdown">'
+ '<textarea id="textarea" data-toggle="dropdown"></textarea>'
+ '<ul class="dropdown-menu" role="menu">'
+ '<li class="dropdown-item"><a id="item1" href="#">Secondary link</a></li>'
+ '<li class="dropdown-item"><a id="item2" href="#">Something else here</a></li>'
+ '<li class="divider"/>'
+ '<li class="dropdown-item"><a href="#">Another link</a></li>'
+ '</ul>'
+ '</li>'
+ '</ul>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()

var $textarea = $('#textarea')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {
assert.ok(true, 'shown was fired')

// Key space
$textarea.trigger('focus').trigger($.Event('keydown', { which: 32 }))
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
assert.ok($(document.activeElement).is($textarea), 'textarea is still focused')

// Key escape
$textarea.trigger('focus').trigger($.Event('keydown', { which: 27 }))
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is not shown')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {

// Key down
$textarea.trigger('focus').trigger($.Event('keydown', { which: 40 }))
assert.ok(document.activeElement === $('#item1')[0], 'item1 is focused')

$dropdown
.parent('.dropdown')
.one('shown.bs.dropdown', function () {

// Key up
$textarea.trigger('focus').trigger($.Event('keydown', { which: 38 }))
assert.ok(document.activeElement === $('#item1')[0], 'item1 is focused')
done()
}).bootstrapDropdown('toggle')
$textarea.trigger('click')
})
$textarea.trigger('click')
})
$textarea.trigger('click')
})

QUnit.test('should skip disabled element when using keyboard navigation', function (assert) {
assert.expect(2)
var done = assert.async()
Expand Down