1
1
import Popper from 'popper.js'
2
-
import { BvEvent } from '../utils/bv-event.class'
3
2
import KeyCodes from '../utils/key-codes'
4
3
import warn from '../utils/warn'
5
-
import { closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
4
+
import { BvEvent } from '../utils/bv-event.class'
5
+
import { closest, contains, isVisible, requestAF, selectAll, eventOn, eventOff } from '../utils/dom'
6
6
import { isNull } from '../utils/inspect'
7
-
import clickOutMixin from './click-out'
8
-
import focusInMixin from './focus-in'
7
+
import idMixin from './id'
9
8
10
9
// Return an array of visible items
11
10
const filterVisibles = els => (els || []).filter(isVisible)
12
11
12
+
// Root dropdown event names
13
+
const ROOT_DROPDOWN_PREFIX = 'bv::dropdown::'
14
+
const ROOT_DROPDOWN_SHOWN = `${ROOT_DROPDOWN_PREFIX}shown`
15
+
const ROOT_DROPDOWN_HIDDEN = `${ROOT_DROPDOWN_PREFIX}hidden`
16
+
17
+
// Delay when loosing focus before closing menu (in ms)
18
+
const FOCUSOUT_DELAY = 100
19
+
13
20
// Dropdown item CSS selectors
14
21
const Selector = {
15
22
FORM_CHILD: '.dropdown form',
@@ -40,7 +47,7 @@ const AttachmentMap = {
40
47
41
48
// @vue/component
42
49
export default {
43
-
mixins: [clickOutMixin, focusInMixin],
50
+
mixins: [idMixin],
44
51
provide() {
45
52
return {
46
53
bvDropdown: this
@@ -136,7 +143,8 @@ export default {
136
143
cancelable: true,
137
144
vueTarget: this,
138
145
target: this.$refs.menu,
139
-
relatedTarget: null
146
+
relatedTarget: null,
147
+
componentId: this.safeId ? this.safeId() : this.id || null
140
148
})
141
149
this.emitEvent(bvEvt)
142
150
if (bvEvt.defaultPrevented) {
@@ -181,16 +189,13 @@ export default {
181
189
emitEvent(bvEvt) {
182
190
const type = bvEvt.type
183
191
this.$emit(type, bvEvt)
184
-
this.$root.$emit(`bv::dropdown::${type}`, bvEvt)
192
+
this.$root.$emit(`${ROOT_DROPDOWN_PREFIX}${type}`, bvEvt)
185
193
},
186
194
showMenu() {
187
195
if (this.disabled) {
188
196
/* istanbul ignore next */
189
197
return
190
198
}
191
-
// Ensure other menus are closed
192
-
this.$root.$emit('bv::dropdown::shown', this)
193
-
194
199
// Are we in a navbar ?
195
200
if (isNull(this.inNavbar) && this.isNav) {
196
201
// We should use an injection for this
@@ -213,6 +218,9 @@ export default {
213
218
}
214
219
}
215
220
221
+
// Ensure other menus are closed
222
+
this.$root.$emit(ROOT_DROPDOWN_SHOWN, this)
223
+
216
224
this.whileOpenListen(true)
217
225
218
226
// Wrap in nextTick to ensure menu is fully rendered/shown
@@ -225,7 +233,7 @@ export default {
225
233
},
226
234
hideMenu() {
227
235
this.whileOpenListen(false)
228
-
this.$root.$emit('bv::dropdown::hidden', this)
236
+
this.$root.$emit(ROOT_DROPDOWN_HIDDEN, this)
229
237
this.$emit('hidden')
230
238
this.removePopper()
231
239
},
@@ -263,19 +271,16 @@ export default {
263
271
}
264
272
return { ...popperConfig, ...(this.popperOpts || {}) }
265
273
},
266
-
whileOpenListen(open) {
274
+
whileOpenListen(isOpen) {
267
275
// turn listeners on/off while open
268
-
if (open) {
276
+
if (isOpen) {
269
277
// If another dropdown is opened
270
-
this.$root.$on('bv::dropdown::shown', this.rootCloseListener)
271
-
// Hide the dropdown when clicked outside
272
-
this.listenForClickOut = true
273
-
// Hide the dropdown when it loses focus
274
-
this.listenForFocusIn = true
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 })
275
281
} else {
276
-
this.$root.$off('bv::dropdown::shown', this.rootCloseListener)
277
-
this.listenForClickOut = false
278
-
this.listenForFocusIn = false
282
+
this.$root.$off(ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
283
+
eventOff(this.$el, 'focusout', this.onFocusOut, { passive: true })
279
284
}
280
285
},
281
286
rootCloseListener(vm) {
@@ -360,6 +365,7 @@ export default {
360
365
this.focusNext(evt, true)
361
366
}
362
367
},
368
+
// If uses presses ESC to close menu
363
369
onEsc(evt) {
364
370
if (this.visible) {
365
371
this.visible = false
@@ -369,18 +375,25 @@ export default {
369
375
this.$once('hidden', this.focusToggler)
370
376
}
371
377
},
372
-
// Document click out listener
373
-
clickOutHandler() {
374
-
if (this.visible) {
375
-
this.visible = false
376
-
}
377
-
},
378
-
// Document focusin listener
379
-
focusInHandler(evt) {
380
-
const target = evt.target
381
-
// If focus leaves dropdown, hide it
382
-
if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
383
-
this.visible = false
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
+
) {
388
+
const doHide = () => {
389
+
this.visible = false
390
+
}
391
+
// When we are in a navbar (which has been responsively stacked), we
392
+
// delay the dropdown's closing so that the next element has a chance
393
+
// to have it's click handler fired (in case it's position moves on
394
+
// the screen do to a navbar menu above it collapsing)
395
+
// https://github.com/bootstrap-vue/bootstrap-vue/issues/4113
396
+
this.inNavbar ? setTimeout(doHide, FOCUSOUT_DELAY) : doHide()
384
397
}
385
398
},
386
399
// Keyboard nav
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