corrade-http-templates – Blame information for rev 61

Subversion Repositories:
Rev:
Rev Author Line No. Line
61 office 1 import $ from 'jquery'
2 import Popper from 'popper.js'
3 import Util from './util'
4  
5 /**
6 * --------------------------------------------------------------------------
7 * Bootstrap (v4.1.3): dropdown.js
8 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
9 * --------------------------------------------------------------------------
10 */
11  
12 const Dropdown = (($) => {
13 /**
14 * ------------------------------------------------------------------------
15 * Constants
16 * ------------------------------------------------------------------------
17 */
18  
19 const NAME = 'dropdown'
20 const VERSION = '4.1.3'
21 const DATA_KEY = 'bs.dropdown'
22 const EVENT_KEY = `.${DATA_KEY}`
23 const DATA_API_KEY = '.data-api'
24 const JQUERY_NO_CONFLICT = $.fn[NAME]
25 const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
26 const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key
27 const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key
28 const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
29 const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
30 const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
31 const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)
32  
33 const Event = {
34 HIDE : `hide${EVENT_KEY}`,
35 HIDDEN : `hidden${EVENT_KEY}`,
36 SHOW : `show${EVENT_KEY}`,
37 SHOWN : `shown${EVENT_KEY}`,
38 CLICK : `click${EVENT_KEY}`,
39 CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
40 KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,
41 KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}`
42 }
43  
44 const ClassName = {
45 DISABLED : 'disabled',
46 SHOW : 'show',
47 DROPUP : 'dropup',
48 DROPRIGHT : 'dropright',
49 DROPLEFT : 'dropleft',
50 MENURIGHT : 'dropdown-menu-right',
51 MENULEFT : 'dropdown-menu-left',
52 POSITION_STATIC : 'position-static'
53 }
54  
55 const Selector = {
56 DATA_TOGGLE : '[data-toggle="dropdown"]',
57 FORM_CHILD : '.dropdown form',
58 MENU : '.dropdown-menu',
59 NAVBAR_NAV : '.navbar-nav',
60 VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
61 }
62  
63 const AttachmentMap = {
64 TOP : 'top-start',
65 TOPEND : 'top-end',
66 BOTTOM : 'bottom-start',
67 BOTTOMEND : 'bottom-end',
68 RIGHT : 'right-start',
69 RIGHTEND : 'right-end',
70 LEFT : 'left-start',
71 LEFTEND : 'left-end'
72 }
73  
74 const Default = {
75 offset : 0,
76 flip : true,
77 boundary : 'scrollParent',
78 reference : 'toggle',
79 display : 'dynamic'
80 }
81  
82 const DefaultType = {
83 offset : '(number|string|function)',
84 flip : 'boolean',
85 boundary : '(string|element)',
86 reference : '(string|element)',
87 display : 'string'
88 }
89  
90 /**
91 * ------------------------------------------------------------------------
92 * Class Definition
93 * ------------------------------------------------------------------------
94 */
95  
96 class Dropdown {
97 constructor(element, config) {
98 this._element = element
99 this._popper = null
100 this._config = this._getConfig(config)
101 this._menu = this._getMenuElement()
102 this._inNavbar = this._detectNavbar()
103  
104 this._addEventListeners()
105 }
106  
107 // Getters
108  
109 static get VERSION() {
110 return VERSION
111 }
112  
113 static get Default() {
114 return Default
115 }
116  
117 static get DefaultType() {
118 return DefaultType
119 }
120  
121 // Public
122  
123 toggle() {
124 if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED)) {
125 return
126 }
127  
128 const parent = Dropdown._getParentFromElement(this._element)
129 const isActive = $(this._menu).hasClass(ClassName.SHOW)
130  
131 Dropdown._clearMenus()
132  
133 if (isActive) {
134 return
135 }
136  
137 const relatedTarget = {
138 relatedTarget: this._element
139 }
140 const showEvent = $.Event(Event.SHOW, relatedTarget)
141  
142 $(parent).trigger(showEvent)
143  
144 if (showEvent.isDefaultPrevented()) {
145 return
146 }
147  
148 // Disable totally Popper.js for Dropdown in Navbar
149 if (!this._inNavbar) {
150 /**
151 * Check for Popper dependency
152 * Popper - https://popper.js.org
153 */
154 if (typeof Popper === 'undefined') {
155 throw new TypeError('Bootstrap dropdown require Popper.js (https://popper.js.org)')
156 }
157  
158 let referenceElement = this._element
159  
160 if (this._config.reference === 'parent') {
161 referenceElement = parent
162 } else if (Util.isElement(this._config.reference)) {
163 referenceElement = this._config.reference
164  
165 // Check if it's jQuery element
166 if (typeof this._config.reference.jquery !== 'undefined') {
167 referenceElement = this._config.reference[0]
168 }
169 }
170  
171 // If boundary is not `scrollParent`, then set position to `static`
172 // to allow the menu to "escape" the scroll parent's boundaries
173 // https://github.com/twbs/bootstrap/issues/24251
174 if (this._config.boundary !== 'scrollParent') {
175 $(parent).addClass(ClassName.POSITION_STATIC)
176 }
177 this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig())
178 }
179  
180 // If this is a touch-enabled device we add extra
181 // empty mouseover listeners to the body's immediate children;
182 // only needed because of broken event delegation on iOS
183 // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
184 if ('ontouchstart' in document.documentElement &&
185 $(parent).closest(Selector.NAVBAR_NAV).length === 0) {
186 $(document.body).children().on('mouseover', null, $.noop)
187 }
188  
189 this._element.focus()
190 this._element.setAttribute('aria-expanded', true)
191  
192 $(this._menu).toggleClass(ClassName.SHOW)
193 $(parent)
194 .toggleClass(ClassName.SHOW)
195 .trigger($.Event(Event.SHOWN, relatedTarget))
196 }
197  
198 dispose() {
199 $.removeData(this._element, DATA_KEY)
200 $(this._element).off(EVENT_KEY)
201 this._element = null
202 this._menu = null
203 if (this._popper !== null) {
204 this._popper.destroy()
205 this._popper = null
206 }
207 }
208  
209 update() {
210 this._inNavbar = this._detectNavbar()
211 if (this._popper !== null) {
212 this._popper.scheduleUpdate()
213 }
214 }
215  
216 // Private
217  
218 _addEventListeners() {
219 $(this._element).on(Event.CLICK, (event) => {
220 event.preventDefault()
221 event.stopPropagation()
222 this.toggle()
223 })
224 }
225  
226 _getConfig(config) {
227 config = {
228 ...this.constructor.Default,
229 ...$(this._element).data(),
230 ...config
231 }
232  
233 Util.typeCheckConfig(
234 NAME,
235 config,
236 this.constructor.DefaultType
237 )
238  
239 return config
240 }
241  
242 _getMenuElement() {
243 if (!this._menu) {
244 const parent = Dropdown._getParentFromElement(this._element)
245 if (parent) {
246 this._menu = parent.querySelector(Selector.MENU)
247 }
248 }
249 return this._menu
250 }
251  
252 _getPlacement() {
253 const $parentDropdown = $(this._element.parentNode)
254 let placement = AttachmentMap.BOTTOM
255  
256 // Handle dropup
257 if ($parentDropdown.hasClass(ClassName.DROPUP)) {
258 placement = AttachmentMap.TOP
259 if ($(this._menu).hasClass(ClassName.MENURIGHT)) {
260 placement = AttachmentMap.TOPEND
261 }
262 } else if ($parentDropdown.hasClass(ClassName.DROPRIGHT)) {
263 placement = AttachmentMap.RIGHT
264 } else if ($parentDropdown.hasClass(ClassName.DROPLEFT)) {
265 placement = AttachmentMap.LEFT
266 } else if ($(this._menu).hasClass(ClassName.MENURIGHT)) {
267 placement = AttachmentMap.BOTTOMEND
268 }
269 return placement
270 }
271  
272 _detectNavbar() {
273 return $(this._element).closest('.navbar').length > 0
274 }
275  
276 _getPopperConfig() {
277 const offsetConf = {}
278 if (typeof this._config.offset === 'function') {
279 offsetConf.fn = (data) => {
280 data.offsets = {
281 ...data.offsets,
282 ...this._config.offset(data.offsets) || {}
283 }
284 return data
285 }
286 } else {
287 offsetConf.offset = this._config.offset
288 }
289  
290 const popperConfig = {
291 placement: this._getPlacement(),
292 modifiers: {
293 offset: offsetConf,
294 flip: {
295 enabled: this._config.flip
296 },
297 preventOverflow: {
298 boundariesElement: this._config.boundary
299 }
300 }
301 }
302  
303 // Disable Popper.js if we have a static display
304 if (this._config.display === 'static') {
305 popperConfig.modifiers.applyStyle = {
306 enabled: false
307 }
308 }
309 return popperConfig
310 }
311  
312 // Static
313  
314 static _jQueryInterface(config) {
315 return this.each(function () {
316 let data = $(this).data(DATA_KEY)
317 const _config = typeof config === 'object' ? config : null
318  
319 if (!data) {
320 data = new Dropdown(this, _config)
321 $(this).data(DATA_KEY, data)
322 }
323  
324 if (typeof config === 'string') {
325 if (typeof data[config] === 'undefined') {
326 throw new TypeError(`No method named "${config}"`)
327 }
328 data[config]()
329 }
330 })
331 }
332  
333 static _clearMenus(event) {
334 if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
335 event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
336 return
337 }
338  
339 const toggles = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))
340 for (let i = 0, len = toggles.length; i < len; i++) {
341 const parent = Dropdown._getParentFromElement(toggles[i])
342 const context = $(toggles[i]).data(DATA_KEY)
343 const relatedTarget = {
344 relatedTarget: toggles[i]
345 }
346  
347 if (event && event.type === 'click') {
348 relatedTarget.clickEvent = event
349 }
350  
351 if (!context) {
352 continue
353 }
354  
355 const dropdownMenu = context._menu
356 if (!$(parent).hasClass(ClassName.SHOW)) {
357 continue
358 }
359  
360 if (event && (event.type === 'click' &&
361 /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) &&
362 $.contains(parent, event.target)) {
363 continue
364 }
365  
366 const hideEvent = $.Event(Event.HIDE, relatedTarget)
367 $(parent).trigger(hideEvent)
368 if (hideEvent.isDefaultPrevented()) {
369 continue
370 }
371  
372 // If this is a touch-enabled device we remove the extra
373 // empty mouseover listeners we added for iOS support
374 if ('ontouchstart' in document.documentElement) {
375 $(document.body).children().off('mouseover', null, $.noop)
376 }
377  
378 toggles[i].setAttribute('aria-expanded', 'false')
379  
380 $(dropdownMenu).removeClass(ClassName.SHOW)
381 $(parent)
382 .removeClass(ClassName.SHOW)
383 .trigger($.Event(Event.HIDDEN, relatedTarget))
384 }
385 }
386  
387 static _getParentFromElement(element) {
388 let parent
389 const selector = Util.getSelectorFromElement(element)
390  
391 if (selector) {
392 parent = document.querySelector(selector)
393 }
394  
395 return parent || element.parentNode
396 }
397  
398 // eslint-disable-next-line complexity
399 static _dataApiKeydownHandler(event) {
400 // If not input/textarea:
401 // - And not a key in REGEXP_KEYDOWN => not a dropdown command
402 // If input/textarea:
403 // - If space key => not a dropdown command
404 // - If key is other than escape
405 // - If key is not up or down => not a dropdown command
406 // - If trigger inside the menu => not a dropdown command
407 if (/input|textarea/i.test(event.target.tagName)
408 ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE &&
409 (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE ||
410 $(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {
411 return
412 }
413  
414 event.preventDefault()
415 event.stopPropagation()
416  
417 if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
418 return
419 }
420  
421 const parent = Dropdown._getParentFromElement(this)
422 const isActive = $(parent).hasClass(ClassName.SHOW)
423  
424 if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
425 isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {
426 if (event.which === ESCAPE_KEYCODE) {
427 const toggle = parent.querySelector(Selector.DATA_TOGGLE)
428 $(toggle).trigger('focus')
429 }
430  
431 $(this).trigger('click')
432 return
433 }
434  
435 const items = [].slice.call(parent.querySelectorAll(Selector.VISIBLE_ITEMS))
436  
437 if (items.length === 0) {
438 return
439 }
440  
441 let index = items.indexOf(event.target)
442  
443 if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up
444 index--
445 }
446  
447 if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down
448 index++
449 }
450  
451 if (index < 0) {
452 index = 0
453 }
454  
455 items[index].focus()
456 }
457 }
458  
459 /**
460 * ------------------------------------------------------------------------
461 * Data Api implementation
462 * ------------------------------------------------------------------------
463 */
464  
465 $(document)
466 .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
467 .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)
468 .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)
469 .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
470 event.preventDefault()
471 event.stopPropagation()
472 Dropdown._jQueryInterface.call($(this), 'toggle')
473 })
474 .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
475 e.stopPropagation()
476 })
477  
478 /**
479 * ------------------------------------------------------------------------
480 * jQuery
481 * ------------------------------------------------------------------------
482 */
483  
484 $.fn[NAME] = Dropdown._jQueryInterface
485 $.fn[NAME].Constructor = Dropdown
486 $.fn[NAME].noConflict = function () {
487 $.fn[NAME] = JQUERY_NO_CONFLICT
488 return Dropdown._jQueryInterface
489 }
490  
491 return Dropdown
492 })($, Popper)
493  
494 export default Dropdown