@@ -2,8 +2,11 @@ import Popper from 'popper.js'
2
2
import KeyCodes from '../utils/key-codes'
3
3
import warn from '../utils/warn'
4
4
import { BvEvent } from '../utils/bv-event.class'
5
-
import { closest, contains, isVisible, requestAF, selectAll, eventOn, eventOff } from '../utils/dom'
5
+
import { closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
6
+
import { hasTouchSupport } from '../utils/env'
6
7
import { isNull } from '../utils/inspect'
8
+
import clickOutMixin from './click-out'
9
+
import focusInMixin from './focus-in'
7
10
import idMixin from './id'
8
11
9
12
// Return an array of visible items
@@ -15,7 +18,7 @@ const ROOT_DROPDOWN_SHOWN = `${ROOT_DROPDOWN_PREFIX}shown`
15
18
const ROOT_DROPDOWN_HIDDEN = `${ROOT_DROPDOWN_PREFIX}hidden`
16
19
17
20
// Delay when loosing focus before closing menu (in ms)
18
-
const FOCUSOUT_DELAY = 100
21
+
const FOCUSOUT_DELAY = hasTouchSupport ? 450 : 150
19
22
20
23
// Dropdown item CSS selectors
21
24
const Selector = {
@@ -47,7 +50,7 @@ const AttachmentMap = {
47
50
48
51
// @vue/component
49
52
export default {
50
-
mixins: [idMixin],
53
+
mixins: [idMixin, clickOutMixin, focusInMixin],
51
54
provide() {
52
55
return {
53
56
bvDropdown: this
@@ -171,18 +174,21 @@ export default {
171
174
},
172
175
created() {
173
176
// Create non-reactive property
174
-
this._popper = null
177
+
this.$_popper = null
178
+
this.$_hideTimeout = null
179
+
this.$_noop = () => {}
175
180
},
176
181
deactivated() /* istanbul ignore next: not easy to test */ {
177
182
// In case we are inside a `<keep-alive>`
178
183
this.visible = false
179
184
this.whileOpenListen(false)
180
-
this.removePopper()
185
+
this.destroyPopper()
181
186
},
182
187
beforeDestroy() {
183
188
this.visible = false
184
189
this.whileOpenListen(false)
185
-
this.removePopper()
190
+
this.destroyPopper()
191
+
this.clearHideTimeout()
186
192
},
187
193
methods: {
188
194
// Event emitter
@@ -235,18 +241,25 @@ export default {
235
241
this.whileOpenListen(false)
236
242
this.$root.$emit(ROOT_DROPDOWN_HIDDEN, this)
237
243
this.$emit('hidden')
238
-
this.removePopper()
244
+
this.destroyPopper()
239
245
},
240
246
createPopper(element) {
241
-
this.removePopper()
242
-
this._popper = new Popper(element, this.$refs.menu, this.getPopperConfig())
247
+
this.destroyPopper()
248
+
this.$_popper = new Popper(element, this.$refs.menu, this.getPopperConfig())
243
249
},
244
-
removePopper() {
245
-
if (this._popper) {
250
+
destroyPopper() {
251
+
if (this.$_popper) {
246
252
// Ensure popper event listeners are removed cleanly
247
-
this._popper.destroy()
253
+
this.$_popper.destroy()
254
+
}
255
+
this.$_popper = null
256
+
},
257
+
clearHideTimeout() {
258
+
/* istanbul ignore next */
259
+
if (this.$_hideTimeout) {
260
+
clearTimeout(this.$_hideTimeout)
261
+
this.$_hideTimeout = null
248
262
}
249
-
this._popper = null
250
263
},
251
264
getPopperConfig() {
252
265
let placement = AttachmentMap.BOTTOM
@@ -271,17 +284,15 @@ export default {
271
284
}
272
285
return { ...popperConfig, ...(this.popperOpts || {}) }
273
286
},
287
+
// Turn listeners on/off while open
274
288
whileOpenListen(isOpen) {
275
-
// turn listeners on/off while open
276
-
if (isOpen) {
277
-
// If another dropdown is opened
278
-
this.$root.$on(ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
279
-
// Hide the menu when focus moves out
280
-
eventOn(this.$el, 'focusout', this.onFocusOut, { passive: true })
281
-
} else {
282
-
this.$root.$off(ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
283
-
eventOff(this.$el, 'focusout', this.onFocusOut, { passive: true })
284
-
}
289
+
// Hide the dropdown when clicked outside
290
+
this.listenForClickOut = isOpen
291
+
// Hide the dropdown when it loses focus
292
+
this.listenForFocusIn = isOpen
293
+
// Hide the dropdown when another dropdown is opened
294
+
const method = isOpen ? '$on' : '$off'
295
+
this.$root[method](ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
285
296
},
286
297
rootCloseListener(vm) {
287
298
if (vm !== this) {
@@ -375,27 +386,28 @@ export default {
375
386
this.$once('hidden', this.focusToggler)
376
387
}
377
388
},
378
-
// Dropdown wrapper focusOut handler
379
-
onFocusOut(evt) {
380
-
// `relatedTarget` is the element gaining focus
381
-
const relatedTarget = evt.relatedTarget
382
-
// If focus moves outside the menu or toggler, then close menu
383
-
if (
384
-
this.visible &&
385
-
!contains(this.$refs.menu, relatedTarget) &&
386
-
!contains(this.toggler, relatedTarget)
387
-
) {
389
+
// Document click out listener
390
+
clickOutHandler(evt) {
391
+
const target = evt.target
392
+
if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
388
393
const doHide = () => {
389
394
this.visible = false
395
+
return null
390
396
}
391
397
// When we are in a navbar (which has been responsively stacked), we
392
398
// delay the dropdown's closing so that the next element has a chance
393
399
// to have it's click handler fired (in case it's position moves on
394
400
// the screen do to a navbar menu above it collapsing)
395
401
// https://github.com/bootstrap-vue/bootstrap-vue/issues/4113
396
-
this.inNavbar ? setTimeout(doHide, FOCUSOUT_DELAY) : doHide()
402
+
this.clearHideTimeout()
403
+
this.$_hideTimeout = this.inNavbar ? setTimeout(doHide, FOCUSOUT_DELAY) : doHide()
397
404
}
398
405
},
406
+
// Document focusin listener
407
+
focusInHandler(evt) {
408
+
// Shared logic with click-out handler
409
+
this.clickOutHandler(evt)
410
+
},
399
411
// Keyboard nav
400
412
focusNext(evt, up) {
401
413
// Ignore key up/down on form elements
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4