This commit is contained in:
Louis-Maxime Piton 2025-04-15 06:32:41 +00:00 committed by GitHub
commit f32cafc065
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 80 additions and 3 deletions

View File

@ -71,6 +71,7 @@ const PLACEMENT_BOTTOMCENTER = 'bottom'
const Default = {
autoClose: true,
boundary: 'clippingParents',
cycling: true,
display: 'dynamic',
offset: [0, 2],
popperConfig: null,
@ -80,6 +81,7 @@ const Default = {
const DefaultType = {
autoClose: '(boolean|string)',
boundary: '(string|element)',
cycling: 'boolean',
display: 'string',
offset: '(array|string|function)',
popperConfig: '(null|object|function)',
@ -331,9 +333,8 @@ class Dropdown extends BaseComponent {
return
}
// 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()
// Allow cycling with up and down arrows
getNextActiveElement(items, target, key === ARROW_DOWN_KEY, this._config.cycling || !items.includes(target)).focus()
}
// Static

View File

@ -1819,6 +1819,81 @@ describe('Dropdown', () => {
})
})
it('should cycle and focus on the last item when using ArrowUp for the first time, respectively with ArrowDown', () => {
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 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 triggerItem1 = fixtureEl.querySelector('#item1')
const triggerItem2 = fixtureEl.querySelector('#item2')
const keydown = createEvent('keydown')
keydown.key = 'ArrowDown'
const keydown2 = createEvent('keydown')
keydown2.key = 'ArrowUp'
triggerDropdown.dispatchEvent(keydown)
triggerItem1.dispatchEvent(keydown2)
setTimeout(() => {
expect(document.activeElement).toEqual(triggerItem2, 'item2 is focused')
triggerItem2.dispatchEvent(keydown)
setTimeout(() => {
expect(document.activeElement).toEqual(triggerItem1, 'item1 is focused')
resolve()
}, 20)
}, 20)
})
})
it('should not cycle and stay focus on the first item when using ArrowUp and respectively with last item and ArrowDown', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-cycling="false">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 triggerItem1 = fixtureEl.querySelector('#item1')
const triggerItem2 = fixtureEl.querySelector('#item2')
const keydown = createEvent('keydown')
keydown.key = 'ArrowDown'
const keydown2 = createEvent('keydown')
keydown2.key = 'ArrowUp'
triggerDropdown.dispatchEvent(keydown)
triggerItem1.dispatchEvent(keydown2)
setTimeout(() => {
expect(document.activeElement).toEqual(triggerItem1, 'item1 is focused')
triggerItem1.dispatchEvent(keydown)
triggerItem2.dispatchEvent(keydown)
setTimeout(() => {
expect(document.activeElement).toEqual(triggerItem2, 'item2 is focused')
resolve()
}, 20)
}, 20)
})
})
it('should not close the dropdown if the user clicks on a text field within dropdown-menu', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [

View File

@ -1078,6 +1078,7 @@ const dropdownList = [...dropdownElementList].map(dropdownToggleEl => new bootst
| --- | --- | --- | --- |
| `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown: <ul class="my-2"><li>`true` - the dropdown will be closed by clicking outside or inside the dropdown menu.</li><li>`false` - the dropdown will be closed by clicking the toggle button and manually calling `hide` or `toggle` method. (Also will not be closed by pressing <kbd>Esc</kbd> key)</li><li>`'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.</li> <li>`'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.</li></ul> Note: the dropdown can always be closed with the <kbd>Esc</kbd> key. |
| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to Popper's preventOverflow modifier). By default it's `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper's [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). |
| `cycling` | boolean | `true` | Configure cycling among `.dropdown-item` using `up` and `down` arrow. |
| `display` | string | `'dynamic'` | By default, we use Popper for dynamic positioning. Disable this with `static`. |
| `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper's [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). |
| `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. |