]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Fix dropdown escape propagation (#33479)
authoralpadev <2838324+alpadev@users.noreply.github.com>
Thu, 1 Apr 2021 18:44:04 +0000 (20:44 +0200)
committerGitHub <noreply@github.com>
Thu, 1 Apr 2021 18:44:04 +0000 (21:44 +0300)
js/src/dropdown.js
js/tests/unit/dropdown.spec.js

index 97bf6e1099960f8e5f730796b6a87b749da1e2d9..605cbc64db9f286f2b3bd8d7c6b8572a5233db6f 100644 (file)
@@ -443,6 +443,31 @@ class Dropdown extends BaseComponent {
     }
   }
 
+  static selectMenuItem(parent, event) {
+    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, parent).filter(isVisible)
+
+    if (!items.length) {
+      return
+    }
+
+    let index = items.indexOf(event.target)
+
+    // Up
+    if (event.key === ARROW_UP_KEY && index > 0) {
+      index--
+    }
+
+    // Down
+    if (event.key === ARROW_DOWN_KEY && index < items.length - 1) {
+      index++
+    }
+
+    // index is -1 if the first keydown is an ArrowUp
+    index = index === -1 ? 0 : index
+
+    items[index].focus()
+  }
+
   static getParentFromElement(element) {
     return getElementFromSelector(element) || element.parentNode
   }
@@ -463,6 +488,12 @@ class Dropdown extends BaseComponent {
       return
     }
 
+    const isActive = this.classList.contains(CLASS_NAME_SHOW)
+
+    if (!isActive && event.key === ESCAPE_KEY) {
+      return
+    }
+
     event.preventDefault()
     event.stopPropagation()
 
@@ -470,19 +501,16 @@ class Dropdown extends BaseComponent {
       return
     }
 
-    const parent = Dropdown.getParentFromElement(this)
-    const isActive = this.classList.contains(CLASS_NAME_SHOW)
+    const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
 
     if (event.key === ESCAPE_KEY) {
-      const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
-      button.focus()
+      getToggleButton().focus()
       Dropdown.clearMenus()
       return
     }
 
     if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) {
-      const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
-      button.click()
+      getToggleButton().click()
       return
     }
 
@@ -491,28 +519,7 @@ class Dropdown extends BaseComponent {
       return
     }
 
-    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, parent).filter(isVisible)
-
-    if (!items.length) {
-      return
-    }
-
-    let index = items.indexOf(event.target)
-
-    // Up
-    if (event.key === ARROW_UP_KEY && index > 0) {
-      index--
-    }
-
-    // Down
-    if (event.key === ARROW_DOWN_KEY && index < items.length - 1) {
-      index++
-    }
-
-    // index is -1 if the first keydown is an ArrowUp
-    index = index === -1 ? 0 : index
-
-    items[index].focus()
+    Dropdown.selectMenuItem(Dropdown.getParentFromElement(this), event)
   }
 }
 
index ad51d487bf12c0e6015c23e1a5c28f32efdec80f..03532256a3d2bf16a9dddf678dd32b8a9d5c2a0d 100644 (file)
@@ -1671,6 +1671,39 @@ describe('Dropdown', () => {
         done()
       }, 20)
     })
+
+    it('should propagate escape key events if dropdown is closed', done => {
+      fixtureEl.innerHTML = [
+        '<div class="parent">',
+        '  <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>',
+        '</div>'
+      ]
+
+      const parent = fixtureEl.querySelector('.parent')
+      const toggle = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+
+      const parentKeyHandler = jasmine.createSpy('parentKeyHandler')
+
+      parent.addEventListener('keydown', parentKeyHandler)
+      parent.addEventListener('keyup', () => {
+        expect(parentKeyHandler).toHaveBeenCalled()
+        done()
+      })
+
+      const keydownEscape = createEvent('keydown', { bubbles: true })
+      keydownEscape.key = 'Escape'
+      const keyupEscape = createEvent('keyup', { bubbles: true })
+      keyupEscape.key = 'Escape'
+
+      toggle.focus()
+      toggle.dispatchEvent(keydownEscape)
+      toggle.dispatchEvent(keyupEscape)
+    })
   })
 
   describe('jQueryInterface', () => {