-
Pierre Vanduynslager authored
- Create backdrop only if the menu is actually open (do not create it if the show event is prevented) - Drop the backdrop only when the corresponding menu is closed (do not remove if there is no menu to close or if the hide event is prevented)
f2f80512
import Util from './util'
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0-alpha.6): dropdown.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
const Dropdown = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'dropdown'
const VERSION = '4.0.0-alpha.6'
const DATA_KEY = 'bs.dropdown'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
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 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 Event = {
HIDE : `hide${EVENT_KEY}`,
HIDDEN : `hidden${EVENT_KEY}`,
SHOW : `show${EVENT_KEY}`,
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}`
}
const ClassName = {
BACKDROP : 'dropdown-backdrop',
DISABLED : 'disabled',
SHOW : 'show'
}
const Selector = {
BACKDROP : '.dropdown-backdrop',
DATA_TOGGLE : '[data-toggle="dropdown"]',
FORM_CHILD : '.dropdown form',
ROLE_MENU : '[role="menu"]',
ROLE_LISTBOX : '[role="listbox"]',
NAVBAR_NAV : '.navbar-nav',
VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, '
+ '[role="listbox"] li:not(.disabled) a'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Dropdown {
constructor(element) {
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
this._element = element
this._addEventListeners()
}
// getters
static get VERSION() {
return VERSION
}
// public
toggle() {
if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
return false
}
const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW)
Dropdown._clearMenus()
if (isActive) {
return false
}
const relatedTarget = {
relatedTarget : this
}
const showEvent = $.Event(Event.SHOW, relatedTarget)
$(parent).trigger(showEvent)
if (showEvent.isDefaultPrevented()) {
return false
}
// set the backdrop only if the dropdown menu will be opened
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)
}
this.focus()
this.setAttribute('aria-expanded', true)
$(parent).toggleClass(ClassName.SHOW)
$(parent).trigger($.Event(Event.SHOWN, relatedTarget))
return false
}
dispose() {
$.removeData(this._element, DATA_KEY)
$(this._element).off(EVENT_KEY)
this._element = null
}
// private
_addEventListeners() {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$(this._element).on(Event.CLICK, this.toggle)
}
// static
static _jQueryInterface(config) {
return this.each(function () {
let data = $(this).data(DATA_KEY)
if (!data) {
data = new Dropdown(this)
$(this).data(DATA_KEY, data)
}
if (typeof config === 'string') {
if (data[config] === undefined) {
throw new Error(`No method named "${config}"`)
}
data[config].call(this)
}
})
}
static _clearMenus(event) {
if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) {
return
}
const toggles = $.makeArray($(Selector.DATA_TOGGLE))
for (let i = 0; i < toggles.length; i++) {
const parent = Dropdown._getParentFromElement(toggles[i])
const relatedTarget = {
relatedTarget : toggles[i]
}
if (!$(parent).hasClass(ClassName.SHOW)) {
continue
}
if (event && (event.type === 'click' &&
/input|textarea/i.test(event.target.tagName) || event.type === 'focusin')
&& $.contains(parent, event.target)) {
continue
}
const hideEvent = $.Event(Event.HIDE, relatedTarget)
$(parent).trigger(hideEvent)
if (hideEvent.isDefaultPrevented()) {
continue
}
// remove backdrop only if the dropdown menu will be hidden
const backdrop = $(parent).find(Selector.BACKDROP)[0]
if (backdrop) {
backdrop.parentNode.removeChild(backdrop)
}
toggles[i].setAttribute('aria-expanded', 'false')
$(parent)
.removeClass(ClassName.SHOW)
.trigger($.Event(Event.HIDDEN, relatedTarget))
}
}
static _getParentFromElement(element) {
let parent
const selector = Util.getSelectorFromElement(element)
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
if (selector) {
parent = $(selector)[0]
}
return parent || element.parentNode
}
static _dataApiKeydownHandler(event) {
if (!REGEXP_KEYDOWN.test(event.which) ||
/input|textarea/i.test(event.target.tagName)) {
return
}
event.preventDefault()
event.stopPropagation()
if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
return
}
const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW)
if (!isActive && event.which !== ESCAPE_KEYCODE ||
isActive && event.which === ESCAPE_KEYCODE) {
if (event.which === ESCAPE_KEYCODE) {
const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
$(toggle).trigger('focus')
}
$(this).trigger('click')
return
}
const items = $(parent).find(Selector.VISIBLE_ITEMS).get()
if (!items.length) {
return
}
let index = items.indexOf(event.target)
if (event.which === ARROW_UP_KEYCODE && index > 0) { // up
index--
}
if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down
index++
}
if (index < 0) {
index = 0
}
items[index].focus()
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
$(document)
.on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
281282283284285286287288289290291292293294295296297298299300301302303304305306307308
.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)
.on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
.on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
e.stopPropagation()
})
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
*/
$.fn[NAME] = Dropdown._jQueryInterface
$.fn[NAME].Constructor = Dropdown
$.fn[NAME].noConflict = function () {
$.fn[NAME] = JQUERY_NO_CONFLICT
return Dropdown._jQueryInterface
}
return Dropdown
})(jQuery)
export default Dropdown