]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Support Popper virtual elements (#32376)
authorNils K <24257556+septatrix@users.noreply.github.com>
Mon, 21 Dec 2020 12:32:11 +0000 (13:32 +0100)
committerGitHub <noreply@github.com>
Mon, 21 Dec 2020 12:32:11 +0000 (14:32 +0200)
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>
js/src/dropdown.js
js/tests/unit/dropdown.spec.js
site/content/docs/5.0/components/dropdowns.md

index 4d65008f82640ff4a3f6738ec9a926a9d8e850a0..008294e9b1c64b37da3934d702cbd011453f9671 100644 (file)
@@ -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)'
 }
@@ -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())
@@ -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
   }
 
index d2171f3697d7724e2b371a78d39c8960b9769828..ba1d0f4438f2525a7f1ddec5d0277390be401b71 100644 (file)
@@ -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">',
index 50dcde2baff36132071590458cc7fb882939a660..a50dcf0f5c00621750594ea940e912354dc5a657 100644 (file)
@@ -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>