]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Use next dropdown menu instead of first of the parent
authorMartijn Cuppens <martijn.cuppens@gmail.com>
Sat, 1 Feb 2020 13:56:20 +0000 (14:56 +0100)
committerMartijn Cuppens <martijn.cuppens@gmail.com>
Mon, 23 Mar 2020 14:35:07 +0000 (15:35 +0100)
js/src/dom/selector-engine.js
js/src/dropdown.js
js/tests/unit/dom/selector-engine.spec.js
js/tests/unit/dropdown.spec.js
scss/mixins/_buttons.scss

index a8c14ae4c81419bb64b8028282b73d2033a90d55..c18ec4136fc2dff4d337e56274853066bb6d6b86 100644 (file)
@@ -66,6 +66,20 @@ const SelectorEngine = {
       previous = previous.previousElementSibling
     }
 
+    return []
+  },
+
+  next(element, selector) {
+    let next = element.nextElementSibling
+
+    while (next) {
+      if (this.matches(next, selector)) {
+        return [next]
+      }
+
+      next = next.nextElementSibling
+    }
+
     return []
   }
 }
index 9d6f8a329667b79b286e6fcfea2aca7d19c1180a..b8f8c22b8c656b926f01138b2992d71daa2528e6 100644 (file)
@@ -129,7 +129,7 @@ class Dropdown {
       return
     }
 
-    const isActive = this._menu.classList.contains(CLASS_NAME_SHOW)
+    const isActive = this._element.classList.contains(CLASS_NAME_SHOW)
 
     Dropdown.clearMenus()
 
@@ -150,7 +150,7 @@ class Dropdown {
       relatedTarget: this._element
     }
 
-    const showEvent = EventHandler.trigger(parent, EVENT_SHOW, relatedTarget)
+    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)
 
     if (showEvent.defaultPrevented) {
       return
@@ -199,7 +199,7 @@ class Dropdown {
     this._element.setAttribute('aria-expanded', true)
 
     Manipulator.toggleClass(this._menu, CLASS_NAME_SHOW)
-    Manipulator.toggleClass(parent, CLASS_NAME_SHOW)
+    Manipulator.toggleClass(this._element, CLASS_NAME_SHOW)
     EventHandler.trigger(parent, EVENT_SHOWN, relatedTarget)
   }
 
@@ -224,7 +224,7 @@ class Dropdown {
     }
 
     Manipulator.toggleClass(this._menu, CLASS_NAME_SHOW)
-    Manipulator.toggleClass(parent, CLASS_NAME_SHOW)
+    Manipulator.toggleClass(this._element, CLASS_NAME_SHOW)
     EventHandler.trigger(parent, EVENT_HIDDEN, relatedTarget)
   }
 
@@ -273,9 +273,7 @@ class Dropdown {
   }
 
   _getMenuElement() {
-    const parent = Dropdown.getParentFromElement(this._element)
-
-    return SelectorEngine.findOne(SELECTOR_MENU, parent)
+    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
   }
 
   _getPlacement() {
@@ -397,14 +395,14 @@ class Dropdown {
       }
 
       const dropdownMenu = context._menu
-      if (!parent.classList.contains(CLASS_NAME_SHOW)) {
+      if (!toggles[i].classList.contains(CLASS_NAME_SHOW)) {
         continue
       }
 
       if (event && ((event.type === 'click' &&
           /input|textarea/i.test(event.target.tagName)) ||
           (event.type === 'keyup' && event.which === TAB_KEYCODE)) &&
-          parent.contains(event.target)) {
+          dropdownMenu.contains(event.target)) {
         continue
       }
 
@@ -427,7 +425,7 @@ class Dropdown {
       }
 
       dropdownMenu.classList.remove(CLASS_NAME_SHOW)
-      parent.classList.remove(CLASS_NAME_SHOW)
+      toggles[i].classList.remove(CLASS_NAME_SHOW)
       EventHandler.trigger(parent, EVENT_HIDDEN, relatedTarget)
     }
   }
@@ -460,13 +458,16 @@ class Dropdown {
     }
 
     const parent = Dropdown.getParentFromElement(this)
-    const isActive = parent.classList.contains(CLASS_NAME_SHOW)
+    const isActive = this.classList.contains(CLASS_NAME_SHOW)
 
-    if (!isActive || (isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE))) {
-      if (event.which === ESCAPE_KEYCODE) {
-        SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, parent).focus()
-      }
+    if (event.which === ESCAPE_KEYCODE) {
+      const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
+      button.focus()
+      Dropdown.clearMenus()
+      return
+    }
 
+    if (!isActive || event.which === SPACE_KEYCODE) {
       Dropdown.clearMenus()
       return
     }
@@ -478,7 +479,7 @@ class Dropdown {
       return
     }
 
-    let index = items.indexOf(event.target)
+    let index = items.indexOf(event.target) || 0
 
     if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up
       index--
@@ -488,10 +489,6 @@ class Dropdown {
       index++
     }
 
-    if (index < 0) {
-      index = 0
-    }
-
     items[index].focus()
   }
 
index e140c6a3e8f960dc341e9c59e799944973dc825a..727f106214df426fb1b869d1b2a6ccc0937e36fc 100644 (file)
@@ -126,5 +126,44 @@ describe('SelectorEngine', () => {
       expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])
     })
   })
+
+  describe('next', () => {
+    it('should return next element', () => {
+      fixtureEl.innerHTML = '<div class="test"></div><button class="btn"></button>'
+
+      const btn = fixtureEl.querySelector('.btn')
+      const divTest = fixtureEl.querySelector('.test')
+
+      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
+    })
+
+    it('should return next element with an extra element between', () => {
+      fixtureEl.innerHTML = [
+        '<div class="test"></div>',
+        '<span></span>',
+        '<button class="btn"></button>'
+      ].join('')
+
+      const btn = fixtureEl.querySelector('.btn')
+      const divTest = fixtureEl.querySelector('.test')
+
+      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
+    })
+
+    it('should return next element with comments or text nodes between', () => {
+      fixtureEl.innerHTML = [
+        '<div class="test"></div>',
+        '<!-- Comment-->',
+        'Text',
+        '<button class="btn"></button>',
+        '<button class="btn"></button>'
+      ].join('')
+
+      const btn = fixtureEl.querySelector('.btn')
+      const divTest = fixtureEl.querySelector('.test')
+
+      expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
+    })
+  })
 })
 
index 2762ad21b9e58caaa8f0de87159668cecaef5dd9..e8a7e66ba9ee5e0b29221ff660e205ad5ffd219c 100644 (file)
@@ -139,7 +139,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -171,7 +171,7 @@ describe('Dropdown', () => {
       const dropdown2 = new Dropdown(btnDropdown2)
 
       firstDropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(firstDropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown1.classList.contains('show')).toEqual(true)
         spyOn(dropdown1._popper, 'destroy')
         dropdown2.toggle()
       })
@@ -204,7 +204,7 @@ describe('Dropdown', () => {
       spyOn(EventHandler, 'off')
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         expect(EventHandler.on).toHaveBeenCalled()
 
@@ -212,7 +212,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('hidden.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(false)
+        expect(btnDropdown.classList.contains('show')).toEqual(false)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
         expect(EventHandler.off).toHaveBeenCalled()
 
@@ -238,7 +238,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -261,7 +261,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropupEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropupEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -284,7 +284,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropupEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropupEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -307,7 +307,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       droprightEl.addEventListener('shown.bs.dropdown', () => {
-        expect(droprightEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -330,7 +330,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropleftEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropleftEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -355,7 +355,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -380,7 +380,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -405,7 +405,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
       })
@@ -538,7 +538,7 @@ describe('Dropdown', () => {
       const dropdown = new Dropdown(btnDropdown)
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         done()
       })
 
@@ -983,7 +983,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('shown.bs.dropdown', e => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         expect(showEventTriggered).toEqual(true)
         expect(e.relatedTarget).toEqual(btnDropdown)
@@ -995,7 +995,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('hidden.bs.dropdown', e => {
-        expect(dropdownEl.classList.contains('show')).toEqual(false)
+        expect(btnDropdown.classList.contains('show')).toEqual(false)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
         expect(hideEventTriggered).toEqual(true)
         expect(e.relatedTarget).toEqual(btnDropdown)
@@ -1066,7 +1066,7 @@ describe('Dropdown', () => {
       const dropdownEl = fixtureEl.querySelector('.dropdown')
 
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(true)
+        expect(btnDropdown.classList.contains('show')).toEqual(true)
 
         const keyUp = createEvent('keyup')
 
@@ -1075,7 +1075,7 @@ describe('Dropdown', () => {
       })
 
       dropdownEl.addEventListener('hidden.bs.dropdown', () => {
-        expect(dropdownEl.classList.contains('show')).toEqual(false)
+        expect(btnDropdown.classList.contains('show')).toEqual(false)
         done()
       })
 
@@ -1111,7 +1111,7 @@ describe('Dropdown', () => {
       const btnGroup = last.parentNode
 
       dropdownTestMenu.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownTestMenu.classList.contains('show')).toEqual(true)
+        expect(first.classList.contains('show')).toEqual(true)
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
         document.body.click()
       })
@@ -1122,7 +1122,7 @@ describe('Dropdown', () => {
       })
 
       btnGroup.addEventListener('shown.bs.dropdown', () => {
-        expect(btnGroup.classList.contains('show')).toEqual(true)
+        expect(last.classList.contains('show')).toEqual(true)
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
         document.body.click()
       })
@@ -1162,7 +1162,7 @@ describe('Dropdown', () => {
       const btnGroup = last.parentNode
 
       dropdownTestMenu.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdownTestMenu.classList.contains('show')).toEqual(true, '"show" class added on click')
+        expect(first.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
         const keyUp = createEvent('keyup')
@@ -1177,7 +1177,7 @@ describe('Dropdown', () => {
       })
 
       btnGroup.addEventListener('shown.bs.dropdown', () => {
-        expect(btnGroup.classList.contains('show')).toEqual(true, '"show" class added on click')
+        expect(last.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
         const keyUp = createEvent('keyup')
@@ -1382,12 +1382,12 @@ describe('Dropdown', () => {
       const input = fixtureEl.querySelector('input')
 
       input.addEventListener('click', () => {
-        expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
+        expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         done()
       })
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
+        expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         input.dispatchEvent(createEvent('click'))
       })
 
@@ -1409,12 +1409,12 @@ describe('Dropdown', () => {
       const textarea = fixtureEl.querySelector('textarea')
 
       textarea.addEventListener('click', () => {
-        expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
+        expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         done()
       })
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
+        expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         textarea.dispatchEvent(createEvent('click'))
       })
 
@@ -1492,7 +1492,7 @@ describe('Dropdown', () => {
         input.focus()
         input.dispatchEvent(keyDownEscape)
 
-        expect(dropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown')
+        expect(triggerDropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown')
         done()
       })
 
@@ -1529,7 +1529,7 @@ describe('Dropdown', () => {
 
       setTimeout(() => {
         expect(dropdown.toggle).not.toHaveBeenCalled()
-        expect(triggerDropdown.parentNode.classList.contains('show')).toEqual(false)
+        expect(triggerDropdown.classList.contains('show')).toEqual(false)
         done()
       }, 20)
     })
index 92fe214d14aea3e170b470428f07224919a91530..acf6b450c5000e80ae84697c33e3c072b0ed6af2 100644 (file)
@@ -90,7 +90,7 @@
 
   &:active,
   &.active,
-  .show > &.dropdown-toggle {
+  &.dropdown-toggle.show {
     color: $active-color;
     background-color: $active-background;
     border-color: $active-border;