mirror of
https://github.com/twbs/bootstrap.git
synced 2024-12-10 22:24:19 +01:00
Automatically select an item in the dropdown when using arrow keys (#34052)
This commit is contained in:
parent
8033975548
commit
b39b665072
@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./dist/js/bootstrap.bundle.js",
|
"path": "./dist/js/bootstrap.bundle.js",
|
||||||
"maxSize": "41.25 kB"
|
"maxSize": "41.5 kB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./dist/js/bootstrap.bundle.min.js",
|
"path": "./dist/js/bootstrap.bundle.min.js",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./dist/js/bootstrap.js",
|
"path": "./dist/js/bootstrap.js",
|
||||||
"maxSize": "27.25 kB"
|
"maxSize": "27.5 kB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./dist/js/bootstrap.min.js",
|
"path": "./dist/js/bootstrap.min.js",
|
||||||
|
@ -354,18 +354,16 @@ class Dropdown extends BaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectMenuItem(event) {
|
_selectMenuItem({ key, target }) {
|
||||||
if (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)
|
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextActiveElement(items, event.target, event.key === ARROW_DOWN_KEY, false).focus()
|
// if target isn't included in items (e.g. when expanding the dropdown)
|
||||||
|
// allow cycling to get the last item in case key equals ARROW_UP_KEY
|
||||||
|
getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static
|
// Static
|
||||||
@ -480,17 +478,18 @@ class Dropdown extends BaseComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) {
|
if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) {
|
||||||
|
if (!isActive) {
|
||||||
getToggleButton().click()
|
getToggleButton().click()
|
||||||
|
}
|
||||||
|
|
||||||
|
Dropdown.getInstance(getToggleButton())._selectMenuItem(event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isActive || event.key === SPACE_KEY) {
|
if (!isActive || event.key === SPACE_KEY) {
|
||||||
Dropdown.clearMenus()
|
Dropdown.clearMenus()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dropdown.getInstance(getToggleButton())._selectMenuItem(event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,9 +264,9 @@ const execute = callback => {
|
|||||||
const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
|
const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
|
||||||
let index = list.indexOf(activeElement)
|
let index = list.indexOf(activeElement)
|
||||||
|
|
||||||
// if the element does not exist in the list initialize it as the first element
|
// if the element does not exist in the list return an element depending on the direction and if cycle is allowed
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return list[0]
|
return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const listLength = list.length
|
const listLength = list.length
|
||||||
|
@ -1561,7 +1561,7 @@ describe('Dropdown', () => {
|
|||||||
triggerDropdown.click()
|
triggerDropdown.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should focus on the first element when using ArrowUp for the first time', done => {
|
it('should open the dropdown and focus on the last item when using ArrowUp for the first time', done => {
|
||||||
fixtureEl.innerHTML = [
|
fixtureEl.innerHTML = [
|
||||||
'<div class="dropdown">',
|
'<div class="dropdown">',
|
||||||
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
|
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
|
||||||
@ -1573,19 +1573,44 @@ describe('Dropdown', () => {
|
|||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
|
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
|
||||||
const item1 = fixtureEl.querySelector('#item1')
|
const lastItem = fixtureEl.querySelector('#item2')
|
||||||
|
|
||||||
triggerDropdown.addEventListener('shown.bs.dropdown', () => {
|
triggerDropdown.addEventListener('shown.bs.dropdown', () => {
|
||||||
const keydown = createEvent('keydown')
|
setTimeout(() => {
|
||||||
keydown.key = 'ArrowUp'
|
expect(document.activeElement).toEqual(lastItem, 'item2 is focused')
|
||||||
|
|
||||||
document.activeElement.dispatchEvent(keydown)
|
|
||||||
expect(document.activeElement).toEqual(item1, 'item1 is focused')
|
|
||||||
|
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
triggerDropdown.click()
|
const keydown = createEvent('keydown')
|
||||||
|
keydown.key = 'ArrowUp'
|
||||||
|
triggerDropdown.dispatchEvent(keydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should open the dropdown and focus on the first item when using ArrowDown for the first time', done => {
|
||||||
|
fixtureEl.innerHTML = [
|
||||||
|
'<div class="dropdown">',
|
||||||
|
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
|
||||||
|
' <div class="dropdown-menu">',
|
||||||
|
' <a id="item1" class="dropdown-item" href="#">A link</a>',
|
||||||
|
' <a id="item2" class="dropdown-item" href="#">Another link</a>',
|
||||||
|
' </div>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
|
||||||
|
const firstItem = fixtureEl.querySelector('#item1')
|
||||||
|
|
||||||
|
triggerDropdown.addEventListener('shown.bs.dropdown', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(document.activeElement).toEqual(firstItem, 'item1 is focused')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const keydown = createEvent('keydown')
|
||||||
|
keydown.key = 'ArrowDown'
|
||||||
|
triggerDropdown.dispatchEvent(keydown)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not close the dropdown if the user clicks on a text field within dropdown-menu', done => {
|
it('should not close the dropdown if the user clicks on a text field within dropdown-menu', done => {
|
||||||
|
@ -661,11 +661,22 @@ describe('Util', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('getNextActiveElement', () => {
|
describe('getNextActiveElement', () => {
|
||||||
it('should return first element if active not exists or not given', () => {
|
it('should return first element if active not exists or not given and shouldGetNext is either true, or false with cycling being disabled', () => {
|
||||||
const array = ['a', 'b', 'c', 'd']
|
const array = ['a', 'b', 'c', 'd']
|
||||||
|
|
||||||
expect(Util.getNextActiveElement(array, '', true, true)).toEqual('a')
|
expect(Util.getNextActiveElement(array, '', true, true)).toEqual('a')
|
||||||
expect(Util.getNextActiveElement(array, 'g', true, true)).toEqual('a')
|
expect(Util.getNextActiveElement(array, 'g', true, true)).toEqual('a')
|
||||||
|
expect(Util.getNextActiveElement(array, '', true, false)).toEqual('a')
|
||||||
|
expect(Util.getNextActiveElement(array, 'g', true, false)).toEqual('a')
|
||||||
|
expect(Util.getNextActiveElement(array, '', false, false)).toEqual('a')
|
||||||
|
expect(Util.getNextActiveElement(array, 'g', false, false)).toEqual('a')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return last element if active not exists or not given and shouldGetNext is false but cycling is enabled', () => {
|
||||||
|
const array = ['a', 'b', 'c', 'd']
|
||||||
|
|
||||||
|
expect(Util.getNextActiveElement(array, '', false, true)).toEqual('d')
|
||||||
|
expect(Util.getNextActiveElement(array, 'g', false, true)).toEqual('d')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return next element or same if is last', () => {
|
it('should return next element or same if is last', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user