]> git.ipfire.org Git - ipfire.org.git/blob - src/scss/bootstrap-4.0.0-alpha.6/js/src/dropdown.js
.gitignore: Add .vscode
[ipfire.org.git] / src / scss / bootstrap-4.0.0-alpha.6 / js / src / dropdown.js
1 import Util from './util'
2
3
4 /**
5 * --------------------------------------------------------------------------
6 * Bootstrap (v4.0.0-alpha.6): dropdown.js
7 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
8 * --------------------------------------------------------------------------
9 */
10
11 const Dropdown = (($) => {
12
13
14 /**
15 * ------------------------------------------------------------------------
16 * Constants
17 * ------------------------------------------------------------------------
18 */
19
20 const NAME = 'dropdown'
21 const VERSION = '4.0.0-alpha.6'
22 const DATA_KEY = 'bs.dropdown'
23 const EVENT_KEY = `.${DATA_KEY}`
24 const DATA_API_KEY = '.data-api'
25 const JQUERY_NO_CONFLICT = $.fn[NAME]
26 const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
27 const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
28 const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
29 const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
30
31 const Event = {
32 HIDE : `hide${EVENT_KEY}`,
33 HIDDEN : `hidden${EVENT_KEY}`,
34 SHOW : `show${EVENT_KEY}`,
35 SHOWN : `shown${EVENT_KEY}`,
36 CLICK : `click${EVENT_KEY}`,
37 CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
38 FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`,
39 KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`
40 }
41
42 const ClassName = {
43 BACKDROP : 'dropdown-backdrop',
44 DISABLED : 'disabled',
45 SHOW : 'show'
46 }
47
48 const Selector = {
49 BACKDROP : '.dropdown-backdrop',
50 DATA_TOGGLE : '[data-toggle="dropdown"]',
51 FORM_CHILD : '.dropdown form',
52 ROLE_MENU : '[role="menu"]',
53 ROLE_LISTBOX : '[role="listbox"]',
54 NAVBAR_NAV : '.navbar-nav',
55 VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, '
56 + '[role="listbox"] li:not(.disabled) a'
57 }
58
59
60 /**
61 * ------------------------------------------------------------------------
62 * Class Definition
63 * ------------------------------------------------------------------------
64 */
65
66 class Dropdown {
67
68 constructor(element) {
69 this._element = element
70
71 this._addEventListeners()
72 }
73
74
75 // getters
76
77 static get VERSION() {
78 return VERSION
79 }
80
81
82 // public
83
84 toggle() {
85 if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
86 return false
87 }
88
89 const parent = Dropdown._getParentFromElement(this)
90 const isActive = $(parent).hasClass(ClassName.SHOW)
91
92 Dropdown._clearMenus()
93
94 if (isActive) {
95 return false
96 }
97
98 if ('ontouchstart' in document.documentElement &&
99 !$(parent).closest(Selector.NAVBAR_NAV).length) {
100
101 // if mobile we use a backdrop because click events don't delegate
102 const dropdown = document.createElement('div')
103 dropdown.className = ClassName.BACKDROP
104 $(dropdown).insertBefore(this)
105 $(dropdown).on('click', Dropdown._clearMenus)
106 }
107
108 const relatedTarget = {
109 relatedTarget : this
110 }
111 const showEvent = $.Event(Event.SHOW, relatedTarget)
112
113 $(parent).trigger(showEvent)
114
115 if (showEvent.isDefaultPrevented()) {
116 return false
117 }
118
119 this.focus()
120 this.setAttribute('aria-expanded', true)
121
122 $(parent).toggleClass(ClassName.SHOW)
123 $(parent).trigger($.Event(Event.SHOWN, relatedTarget))
124
125 return false
126 }
127
128 dispose() {
129 $.removeData(this._element, DATA_KEY)
130 $(this._element).off(EVENT_KEY)
131 this._element = null
132 }
133
134
135 // private
136
137 _addEventListeners() {
138 $(this._element).on(Event.CLICK, this.toggle)
139 }
140
141
142 // static
143
144 static _jQueryInterface(config) {
145 return this.each(function () {
146 let data = $(this).data(DATA_KEY)
147
148 if (!data) {
149 data = new Dropdown(this)
150 $(this).data(DATA_KEY, data)
151 }
152
153 if (typeof config === 'string') {
154 if (data[config] === undefined) {
155 throw new Error(`No method named "${config}"`)
156 }
157 data[config].call(this)
158 }
159 })
160 }
161
162 static _clearMenus(event) {
163 if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) {
164 return
165 }
166
167 const backdrop = $(Selector.BACKDROP)[0]
168 if (backdrop) {
169 backdrop.parentNode.removeChild(backdrop)
170 }
171
172 const toggles = $.makeArray($(Selector.DATA_TOGGLE))
173
174 for (let i = 0; i < toggles.length; i++) {
175 const parent = Dropdown._getParentFromElement(toggles[i])
176 const relatedTarget = {
177 relatedTarget : toggles[i]
178 }
179
180 if (!$(parent).hasClass(ClassName.SHOW)) {
181 continue
182 }
183
184 if (event && (event.type === 'click' &&
185 /input|textarea/i.test(event.target.tagName) || event.type === 'focusin')
186 && $.contains(parent, event.target)) {
187 continue
188 }
189
190 const hideEvent = $.Event(Event.HIDE, relatedTarget)
191 $(parent).trigger(hideEvent)
192 if (hideEvent.isDefaultPrevented()) {
193 continue
194 }
195
196 toggles[i].setAttribute('aria-expanded', 'false')
197
198 $(parent)
199 .removeClass(ClassName.SHOW)
200 .trigger($.Event(Event.HIDDEN, relatedTarget))
201 }
202 }
203
204 static _getParentFromElement(element) {
205 let parent
206 const selector = Util.getSelectorFromElement(element)
207
208 if (selector) {
209 parent = $(selector)[0]
210 }
211
212 return parent || element.parentNode
213 }
214
215 static _dataApiKeydownHandler(event) {
216 if (!/(38|40|27|32)/.test(event.which) ||
217 /input|textarea/i.test(event.target.tagName)) {
218 return
219 }
220
221 event.preventDefault()
222 event.stopPropagation()
223
224 if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
225 return
226 }
227
228 const parent = Dropdown._getParentFromElement(this)
229 const isActive = $(parent).hasClass(ClassName.SHOW)
230
231 if (!isActive && event.which !== ESCAPE_KEYCODE ||
232 isActive && event.which === ESCAPE_KEYCODE) {
233
234 if (event.which === ESCAPE_KEYCODE) {
235 const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
236 $(toggle).trigger('focus')
237 }
238
239 $(this).trigger('click')
240 return
241 }
242
243 const items = $(parent).find(Selector.VISIBLE_ITEMS).get()
244
245 if (!items.length) {
246 return
247 }
248
249 let index = items.indexOf(event.target)
250
251 if (event.which === ARROW_UP_KEYCODE && index > 0) { // up
252 index--
253 }
254
255 if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down
256 index++
257 }
258
259 if (index < 0) {
260 index = 0
261 }
262
263 items[index].focus()
264 }
265
266 }
267
268
269 /**
270 * ------------------------------------------------------------------------
271 * Data Api implementation
272 * ------------------------------------------------------------------------
273 */
274
275 $(document)
276 .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
277 .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler)
278 .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler)
279 .on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus)
280 .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
281 .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
282 e.stopPropagation()
283 })
284
285
286 /**
287 * ------------------------------------------------------------------------
288 * jQuery
289 * ------------------------------------------------------------------------
290 */
291
292 $.fn[NAME] = Dropdown._jQueryInterface
293 $.fn[NAME].Constructor = Dropdown
294 $.fn[NAME].noConflict = function () {
295 $.fn[NAME] = JQUERY_NO_CONFLICT
296 return Dropdown._jQueryInterface
297 }
298
299 return Dropdown
300
301 })(jQuery)
302
303 export default Dropdown