Via data attributes or JavaScript, the dropdown plugin toggles hidden content (dropdown menus) by toggling the `.show` class on the parent list item.
-On touch-enabled devices, opening a dropdown adds a `.dropdown-backdrop` as a tap area for closing dropdown menus when tapping outside the menu, to work around a quirk in iOS' event delegation. **This means that once a dropdown menu is open, any tap or click (including with a mouse, on a multi-input device such as a laptop with a touchscreen) outside of the menu will be intercepted to close the menu. Opening another dropdown menu, or activating any other control or link, will therefore require an extra tap or click.**
+{% callout info %}
+On touch-enabled devices, opening a dropdown adds empty (`$.noop`) `mouseover` handlers to the immediate children of the `<body>` element. This admittedly ugly hack is necessary to work around a [quirk in iOS' event delegation](https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html), which would otherwise prevent a tap anywhere outside of the dropdown from triggering the code that closes the dropdown. Once the dropdown is closed, these additional empty `mouseover` handlers are removed.
+{% endcallout %}
Note: The `data-toggle="dropdown"` attribute is relied on for closing dropdown menus at an application level, so it's a good idea to always use it.
}
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',
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()
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')
white-space: nowrap; // as with > li > a
}
-// Backdrop to catch body clicks on mobile, etc.
-.dropdown-backdrop {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: $zindex-dropdown-backdrop;
-}
-
// Allow for dropdowns to go bottom up (aka, dropup-menu)
//
// Just add .dropup after the standard .dropdown class and you're set.
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
-$zindex-dropdown-backdrop: 990 !default;
$zindex-dropdown: 1000 !default;
$zindex-sticky: 1020 !default;
$zindex-fixed: 1030 !default;