@@ -2,9 +2,10 @@ import Popper from 'popper.js'
22import KeyCodes from '../utils/key-codes'
33import warn from '../utils/warn'
44import { BvEvent } from '../utils/bv-event.class'
5- import { from as arrayFrom } from '../utils/array'
6- import { closest , contains , isVisible , requestAF , selectAll , eventOn , eventOff } from '../utils/dom'
5+ import { closest , contains , isVisible , requestAF , selectAll } from '../utils/dom'
76import { isNull } from '../utils/inspect'
7+ import clickOutMixin from './click-out'
8+ import focusInMixin from './focus-in'
89import idMixin from './id'
910
1011// Return an array of visible items
@@ -15,9 +16,6 @@ const ROOT_DROPDOWN_PREFIX = 'bv::dropdown::'
1516const ROOT_DROPDOWN_SHOWN = `${ ROOT_DROPDOWN_PREFIX } shown`
1617const ROOT_DROPDOWN_HIDDEN = `${ ROOT_DROPDOWN_PREFIX } hidden`
1718
18- // Delay when loosing focus before closing menu (in ms)
19- const FOCUSOUT_DELAY = 100
20-
2119// Dropdown item CSS selectors
2220const Selector = {
2321 FORM_CHILD : '.dropdown form' ,
@@ -48,7 +46,7 @@ const AttachmentMap = {
4846
4947// @vue /component
5048export default {
51- mixins : [ idMixin ] ,
49+ mixins : [ idMixin , clickOutMixin , focusInMixin ] ,
5250 provide ( ) {
5351 return {
5452 bvDropdown : this
@@ -273,31 +271,18 @@ export default {
273271 }
274272 return { ...popperConfig , ...( this . popperOpts || { } ) }
275273 } ,
274+ isDropdownElement ( el ) {
275+ return contains ( this . $refs . menu , el ) || contains ( this . toggler , el )
276+ } ,
277+ // Turn listeners on/off while open
276278 whileOpenListen ( isOpen ) {
277- // turn listeners on/off while open
278- if ( isOpen ) {
279- // If another dropdown is opened
280- this . $root . $on ( ROOT_DROPDOWN_SHOWN , this . rootCloseListener )
281- // Hide the menu when focus moves out
282- eventOn ( this . $el , 'focusout' , this . onFocusOut , { passive : true } )
283- } else {
284- this . $root . $off ( ROOT_DROPDOWN_SHOWN , this . rootCloseListener )
285- eventOff ( this . $el , 'focusout' , this . onFocusOut , { passive : true } )
286- }
287- // Handle special case for touch events on iOS
288- this . setOnTouchStartListener ( isOpen )
289- } ,
290- setOnTouchStartListener ( isOpen ) /* istanbul ignore next: No JSDOM support of `ontouchstart` */ {
291- // If this is a touch-enabled device we add extra empty
292- // `mouseover` listeners to the body's immediate children
293- // Only needed because of broken event delegation on iOS
294- // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
295- if ( 'ontouchstart' in document . documentElement ) {
296- const method = isOpen ? eventOn : eventOff
297- arrayFrom ( document . body . children ) . forEach ( el => {
298- method ( el , 'mouseover' , this . $_noop )
299- } )
300- }
279+ // Hide the dropdown when clicked outside
280+ this . listenForClickOut = isOpen
281+ // Hide the dropdown when it loses focus
282+ this . listenForFocusIn = isOpen
283+ // Hide the dropdown when another dropdown is opened
284+ const method = isOpen ? '$on' : '$off'
285+ this . $root [ method ] ( ROOT_DROPDOWN_SHOWN , this . rootCloseListener )
301286 } ,
302287 rootCloseListener ( vm ) {
303288 if ( vm !== this ) {
@@ -391,25 +376,16 @@ export default {
391376 this . $once ( 'hidden' , this . focusToggler )
392377 }
393378 } ,
394- // Dropdown wrapper focusOut handler
395- onFocusOut ( evt ) {
396- // `relatedTarget` is the element gaining focus
397- const relatedTarget = evt . relatedTarget
398- // If focus moves outside the menu or toggler, then close menu
399- if (
400- this . visible &&
401- ! contains ( this . $refs . menu , relatedTarget ) &&
402- ! contains ( this . toggler , relatedTarget )
403- ) {
404- const doHide = ( ) => {
405- this . visible = false
406- }
407- // When we are in a navbar (which has been responsively stacked), we
408- // delay the dropdown's closing so that the next element has a chance
409- // to have it's click handler fired (in case it's position moves on
410- // the screen do to a navbar menu above it collapsing)
411- // https://github.com/bootstrap-vue/bootstrap-vue/issues/4113
412- this . inNavbar ? setTimeout ( doHide , FOCUSOUT_DELAY ) : doHide ( )
379+ // Document click out listener
380+ clickOutHandler ( evt ) {
381+ if ( this . visible && ! this . isDropdownElement ( evt . target ) ) {
382+ this . visible = false
383+ }
384+ } ,
385+ // Document focusin listener
386+ focusInHandler ( evt ) {
387+ if ( this . visible && ! this . isDropdownElement ( evt . target ) ) {
388+ this . visible = false
413389 }
414390 } ,
415391 // Keyboard nav
0 commit comments