Skip to content

Commit

Permalink
Support Popper virtual elements (twbs#32376)
Browse files Browse the repository at this point in the history
Adds the ability to use objects implementing the virtual element interface as the value for the reference option of a dropdown config.

Co-authored-by: XhmikosR <xhmikosr@gmail.com>
  • Loading branch information
2 people authored and sijusamson committed Jan 4, 2021
1 parent fb42ce5 commit e14d17e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 3 deletions.
11 changes: 10 additions & 1 deletion js/src/dropdown.js
Expand Up @@ -84,7 +84,7 @@ const DefaultType = {
offset: '(number|string|function)',
flip: 'boolean',
boundary: '(string|element)',
reference: '(string|element)',
reference: '(string|element|object)',
display: 'string',
popperConfig: '(null|object)'
}
Expand Down Expand Up @@ -172,6 +172,8 @@ class Dropdown extends BaseComponent {
if (typeof this._config.reference.jquery !== 'undefined') {
referenceElement = this._config.reference[0]
}
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference
}

this._popper = Popper.createPopper(referenceElement, this._menu, this._getPopperConfig())
Expand Down Expand Up @@ -257,6 +259,13 @@ class Dropdown extends BaseComponent {

typeCheckConfig(NAME, config, this.constructor.DefaultType)

if (typeof config.reference === 'object' && !isElement(config.reference) &&
typeof config.reference.getBoundingClientRect !== 'function'
) {
// Popper virtual elements require a getBoundingClientRect method
throw new Error(`${NAME}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
}

return config
}

Expand Down
52 changes: 52 additions & 0 deletions js/tests/unit/dropdown.spec.js
Expand Up @@ -367,6 +367,58 @@ describe('Dropdown', () => {
dropdown.toggle()
})

it('should toggle a dropdown with a valid virtual element reference', done => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle visually-hidden" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
'</div>'
].join('')

const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const virtualElement = {
getBoundingClientRect() {
return {
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
}

expect(() => new Dropdown(btnDropdown, {
reference: {}
})).toThrow()

expect(() => new Dropdown(btnDropdown, {
reference: {
getBoundingClientRect: 'not-a-function'
}
})).toThrow()

// use onFirstUpdate as Poppers internal update is executed async
const dropdown = new Dropdown(btnDropdown, {
reference: virtualElement,
popperConfig: {
onFirstUpdate() {
expect(virtualElement.getBoundingClientRect).toHaveBeenCalled()
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
}
}
})

spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()

dropdown.toggle()
})

it('should not toggle a dropdown if the element is disabled', done => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
Expand Down
4 changes: 2 additions & 2 deletions site/content/docs/5.0/components/dropdowns.md
Expand Up @@ -886,9 +886,9 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
</tr>
<tr>
<td><code>reference</code></td>
<td>string | element</td>
<td>string | element | object</td>
<td><code>'toggle'</code></td>
<td>Reference element of the dropdown menu. Accepts the values of <code>'toggle'</code>, <code>'parent'</code>, or an HTMLElement reference. For more information refer to Popper's <a href="https://popper.js.org/docs/v2/constructors/#createpopper">constructor docs</a>.</td>
<td>Reference element of the dropdown menu. Accepts the values of <code>'toggle'</code>, <code>'parent'</code>, an HTMLElement reference or an object providing <code>getBoundingClientRect</code>. For more information refer to Popper's <a href="https://popper.js.org/docs/v2/constructors/#createpopper">constructor docs</a> and <a href="https://popper.js.org/docs/v2/virtual-elements/">virtual element docs</a>.</td>
</tr>
<tr>
<td><code>display</code></td>
Expand Down

0 comments on commit e14d17e

Please sign in to comment.