From f307fdd2c3ae302417b6cd959a3dd606b456de40 Mon Sep 17 00:00:00 2001 From: alpadev Date: Thu, 25 Mar 2021 16:04:18 +0100 Subject: [PATCH 1/4] refactor: extract the menu item selection logic into its own function this reduces the complexity of the dataApiKeydownHandler to get rid of the eslint warning --- js/src/dropdown.js | 49 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 97bf6e109996..df918adc3aac 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -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 } @@ -470,7 +495,6 @@ class Dropdown extends BaseComponent { return } - const parent = Dropdown.getParentFromElement(this) const isActive = this.classList.contains(CLASS_NAME_SHOW) if (event.key === ESCAPE_KEY) { @@ -491,28 +515,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) } } From 0eaa4130b9712196f02026b7d79f7c3e7aea2993 Mon Sep 17 00:00:00 2001 From: alpadev Date: Thu, 25 Mar 2021 18:26:48 +0100 Subject: [PATCH 2/4] test: add test case for dropdown escape key propagation --- js/tests/unit/dropdown.spec.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js index ad51d487bf12..03532256a3d2 100644 --- a/js/tests/unit/dropdown.spec.js +++ b/js/tests/unit/dropdown.spec.js @@ -1671,6 +1671,39 @@ describe('Dropdown', () => { done() }, 20) }) + + it('should propagate escape key events if dropdown is closed', done => { + fixtureEl.innerHTML = [ + '
', + ' ', + '
' + ] + + 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', () => { From 04022c90726155934c33b9b1af4ade24650dc03f Mon Sep 17 00:00:00 2001 From: alpadev Date: Thu, 25 Mar 2021 18:28:47 +0100 Subject: [PATCH 3/4] fix: escape key should not be handled when the dropdown is closed see #33465 --- js/src/dropdown.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/js/src/dropdown.js b/js/src/dropdown.js index df918adc3aac..31766e46274e 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -488,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() @@ -495,18 +501,16 @@ class Dropdown extends BaseComponent { return } - const isActive = this.classList.contains(CLASS_NAME_SHOW) + const getButton = () => 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() + getButton().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() + getButton().click() return } From 3f658e533d0e12f540fe18eedd3e8a3dca0a4719 Mon Sep 17 00:00:00 2001 From: alpadev Date: Tue, 30 Mar 2021 13:18:44 +0200 Subject: [PATCH 4/4] refactor: changed getButton to getToggleButton requested by rohit2sharma95 - https://github.com/twbs/bootstrap/pull/33479#discussion_r603857306 --- js/src/dropdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 31766e46274e..605cbc64db9f 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -501,16 +501,16 @@ class Dropdown extends BaseComponent { return } - const getButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] + const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] if (event.key === ESCAPE_KEY) { - getButton().focus() + getToggleButton().focus() Dropdown.clearMenus() return } if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) { - getButton().click() + getToggleButton().click() return }