From ab2fc63d08b8c53d6f29bcfd73b7f2d5ceaacacd Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Sun, 22 May 2016 02:16:27 -0400 Subject: Dropdown: remove dependency to role="menu", role="listbox" a and li elements => fix keyboard navigation --- js/src/dropdown.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 92f841bc4..a9786a534 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -44,11 +44,9 @@ const Dropdown = (($) => { BACKDROP : '.dropdown-backdrop', DATA_TOGGLE : '[data-toggle="dropdown"]', FORM_CHILD : '.dropdown form', - ROLE_MENU : '[role="menu"]', - ROLE_LISTBOX : '[role="listbox"]', + MENU : '.dropdown-menu', NAVBAR_NAV : '.navbar-nav', - VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, ' - + '[role="listbox"] li:not(.disabled) a' + VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)' } @@ -268,8 +266,7 @@ const Dropdown = (($) => { $(document) .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) - .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) - .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler) .on(Event.CLICK_DATA_API, Dropdown._clearMenus) .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { -- cgit v1.2.3 From 67958f35e80af425c08fa367e8a6f1eeefb830bc Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Wed, 4 Jan 2017 12:24:33 -0500 Subject: Merge conflict --- js/src/dropdown.js | 6 ------ 1 file changed, 6 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 46287fb90..873c106a7 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -272,14 +272,8 @@ const Dropdown = (($) => { $(document) .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) -<<<<<<< HEAD .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler) - .on(Event.CLICK_DATA_API, Dropdown._clearMenus) -======= - .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) - .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) .on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus) ->>>>>>> twbs/v4-dev .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { e.stopPropagation() -- cgit v1.2.3 From 403f55fba976908cd1fd9dfd8ea4a98d035daaf2 Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Sun, 22 Jan 2017 17:34:54 -0500 Subject: Fix spacebar key in Firefox for button elements --- js/src/dropdown.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 200f31569..5c204f054 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -28,7 +28,7 @@ const Dropdown = (($) => { const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) - const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}|${SPACE_KEYCODE}`) + const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`) const Event = { HIDE : `hide${EVENT_KEY}`, @@ -213,7 +213,7 @@ const Dropdown = (($) => { } static _dataApiKeydownHandler(event) { - if (!REGEXP_KEYDOWN.test(event.which) || + if (!REGEXP_KEYDOWN.test(event.which) && /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE || /input|textarea/i.test(event.target.tagName)) { return } @@ -228,8 +228,8 @@ const Dropdown = (($) => { const parent = Dropdown._getParentFromElement(this) const isActive = $(parent).hasClass(ClassName.SHOW) - if (!isActive && event.which !== ESCAPE_KEYCODE || - isActive && event.which === ESCAPE_KEYCODE) { + if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) || + isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { if (event.which === ESCAPE_KEYCODE) { const toggle = $(parent).find(Selector.DATA_TOGGLE)[0] -- cgit v1.2.3 From 4ab576a41941b720e2f6fa77724203f66a1d3a17 Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Wed, 8 Feb 2017 18:51:50 -0500 Subject: Fixes #21941 --- js/src/dropdown.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 5c204f054..1e85c2530 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -25,6 +25,7 @@ const Dropdown = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key + const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) @@ -37,8 +38,8 @@ const Dropdown = (($) => { SHOWN : `shown${EVENT_KEY}`, CLICK : `click${EVENT_KEY}`, CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, - FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`, - KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}` + KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`, + KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -160,7 +161,8 @@ const Dropdown = (($) => { } static _clearMenus(event) { - if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) { + if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || + event.type === 'keyup' && event.which !== TAB_KEYCODE)) { return } @@ -182,7 +184,7 @@ const Dropdown = (($) => { } if (event && (event.type === 'click' && - /input|textarea/i.test(event.target.tagName) || event.type === 'focusin') + /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) { continue } @@ -213,7 +215,7 @@ const Dropdown = (($) => { } static _dataApiKeydownHandler(event) { - if (!REGEXP_KEYDOWN.test(event.which) && /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE || + if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE || /input|textarea/i.test(event.target.tagName)) { return } @@ -275,7 +277,7 @@ const Dropdown = (($) => { $(document) .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler) - .on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus) + .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus) .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { e.stopPropagation() -- cgit v1.2.3 From 81e12d5715d675b44da4c7d6547583f1d546e491 Mon Sep 17 00:00:00 2001 From: Pierre-Denis Vanduynslager Date: Wed, 12 Apr 2017 09:41:27 -0400 Subject: Indent --- js/src/dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d7eebd8c9..fc2908e7d 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -29,7 +29,7 @@ const Dropdown = (($) => { const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) - const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`) + const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`) const Event = { HIDE : `hide${EVENT_KEY}`, -- cgit v1.2.3 From 3275ca4b30383390d3475beb8c4f43343ab31f5c Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Wed, 12 Apr 2017 13:54:16 +0100 Subject: Reword "mobile" to "touch-enabled" ...as touch is not exclusive to "mobile" anymore nowadays. also explicitly clarifies this is a fix for iOS, and that it impacts touch laptops etc as well. lastly, renames the variable from "dropdown" to "backdrop" for clarity/consistency --- js/src/dropdown.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 644273a0a..96b7c8773 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -112,11 +112,12 @@ const Dropdown = (($) => { if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { - // if mobile we use a backdrop because click events don't delegate - const dropdown = document.createElement('div') - dropdown.className = ClassName.BACKDROP - $(dropdown).insertBefore(this) - $(dropdown).on('click', Dropdown._clearMenus) + // if touch-enabled device we use a backdrop because click events + // don't delegate on iOS - see https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + const backdrop = document.createElement('div') + backdrop.className = ClassName.BACKDROP + $(backdrop).insertBefore(this) + $(backdrop).on('click', Dropdown._clearMenus) } this.focus() -- cgit v1.2.3 From 6d64afe508bfd0bcfb5831a9a4708cef4ad88334 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Fri, 14 Apr 2017 09:19:00 +0100 Subject: Replace dropdown backdrop hack with cleaner JS-only hack * Replace backdrop with simple noop mouse listener As discussed in https://github.com/twbs/bootstrap/pull/22422 the current approach of injecting a backdrop (to work around iOS' broken event delegation for the `click` event) has annoying consequences on touch-enabled laptop/desktop devices. Instead of a backdrop `
`, here we simply add extra empty/noop mouse listeners to the immediate children of `` (and remove them when the dropdown is closed) in order to force iOS to properly bubble a `click` resulting from a tap (essentially, method 2 from https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html) This is sufficient (except in rare cases where the user does manage to tap on the body itself, rather than any child elements of body - which is not very likely in an iOS phone/tablet scenario for most layouts) to get iOS to get a grip and do the correct event bubbling/delegation, meaning the regular "click" event will bubble back to the `` when tapping outside of the dropdown, and the dropdown will close properly (just like it already does, even without this fix, in non-iOS touchscreen devices/browsers, like Chrome/Android and Windows on a touch laptop). This approach, though a bit hacky, has no impact on the DOM structure, and has no unforeseen side effects on touch-enabled laptops/desktops. And crucially, it works just fine in iOS. * Remove dropdown backdrop styles * Update doc for dropdowns and touch-enabled devices --- js/src/dropdown.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index b616186f3..812e718a8 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -43,13 +43,11 @@ const Dropdown = (($) => { } const ClassName = { - BACKDROP : 'dropdown-backdrop', DISABLED : 'disabled', SHOW : 'show' } const Selector = { - BACKDROP : '.dropdown-backdrop', DATA_TOGGLE : '[data-toggle="dropdown"]', FORM_CHILD : '.dropdown form', MENU : '.dropdown-menu', @@ -107,16 +105,13 @@ const Dropdown = (($) => { return false } - // set the backdrop only if the dropdown menu will be opened + // if this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { - - // if touch-enabled device we use a backdrop because click events - // don't delegate on iOS - see https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - const backdrop = document.createElement('div') - backdrop.className = ClassName.BACKDROP - $(backdrop).insertBefore(this) - $(backdrop).on('click', Dropdown._clearMenus) + $('body').children().on('mouseover', '*', $.noop) } this.focus() @@ -192,10 +187,10 @@ const Dropdown = (($) => { continue } - // remove backdrop only if the dropdown menu will be hidden - const backdrop = $(parent).find(Selector.BACKDROP)[0] - if (backdrop) { - backdrop.parentNode.removeChild(backdrop) + // if this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + $('body').children().off('mouseover', '*', $.noop) } toggles[i].setAttribute('aria-expanded', 'false') -- cgit v1.2.3 From 1f37c536b2691e4a98310982f9b58ede506f11d8 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Thu, 20 Apr 2017 14:08:40 +0100 Subject: Tweak iOS hack for dropdown Tweak to https://github.com/twbs/bootstrap/pull/22426, where the wrong selector slipped through the net (selecting all of ``s grand-children rather than children) --- js/src/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src/dropdown.js') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 812e718a8..eb536dc7d 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -111,7 +111,7 @@ const Dropdown = (($) => { // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { - $('body').children().on('mouseover', '*', $.noop) + $('body').children().on('mouseover', null, $.noop) } this.focus() @@ -190,7 +190,7 @@ const Dropdown = (($) => { // if this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { - $('body').children().off('mouseover', '*', $.noop) + $('body').children().off('mouseover', null, $.noop) } toggles[i].setAttribute('aria-expanded', 'false') -- cgit v1.2.3