Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions js/src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ class Dropdown extends BaseComponent {
this._element.setAttribute('aria-expanded', 'false')
Manipulator.removeDataAttribute(this._menu, 'popper')
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)

if (this._menu?.contains(document.activeElement)) {
this._element?.focus()
}
}

_getConfig(config) {
Expand Down
94 changes: 94 additions & 0 deletions js/tests/unit/dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2097,6 +2097,100 @@ describe('Dropdown', () => {
})
})

it('should focus the dropdown trigger when an item is selected (and the dropdown is hidden)', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Some Item</a>',
' </div>',
'</div>'
].join('')

const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const item = fixtureEl.querySelector('.dropdown-item')

toggle.addEventListener('shown.bs.dropdown', () => {
item.focus()
item.click()
})

toggle.addEventListener('hidden.bs.dropdown', () => setTimeout(() => {
expect(document.activeElement).toEqual(toggle)
resolve()
}))

toggle.click()
})
})

it('should not focus the dropdown trigger when an item is selected and something else is focused in the hidden event', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'<button class="focus-target"></button>',
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Some Item</a>',
' </div>',
'</div>'
].join('')

const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const item = fixtureEl.querySelector('.dropdown-item')
const focusTarget = fixtureEl.querySelector('.focus-target')

toggle.addEventListener('shown.bs.dropdown', () => {
item.focus()
item.click()
})

toggle.addEventListener('hidden.bs.dropdown', () => {
focusTarget.focus()
setTimeout(() => {
expect(document.activeElement).toEqual(focusTarget)
expect(document.activeElement).not.toEqual(toggle)
resolve()
})
})

toggle.click()
})
})

it('should not throw an error when the dropdown was disposed in the hidden event', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Some Item</a>',
' </div>',
'</div>'
].join('')

const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const item = fixtureEl.querySelector('.dropdown-item')

toggle.addEventListener('shown.bs.dropdown', () => {
item.click()
})

toggle.addEventListener('hidden.bs.dropdown', () => {
const dropdown = Dropdown.getInstance(toggle)
dropdown.dispose()

setTimeout(() => {
expect(dropdown._menu).toBeNull()
resolve()
})
})

toggle.click()
})
})

it('should close dropdown (only) by clicking inside the dropdown menu when it has data-attribute `data-bs-auto-close="inside"`', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
Expand Down