🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c69ae1a
fix(dropdown): focus-out handling when new focus somes from another `…
jacobmllr95 Sep 21, 2019
799fa89
Try delaying the show handler
tmorehouse Sep 21, 2019
79f715b
Update dropdown.js
tmorehouse Sep 21, 2019
7d362d3
Update dropdown.js
tmorehouse Sep 21, 2019
ce84c4f
Trying with clickout handler disabled
tmorehouse Sep 21, 2019
c72502c
Update dropdown.js
tmorehouse Sep 21, 2019
8277134
Update dropdown.js
tmorehouse Sep 21, 2019
8e2c90e
Update dropdown.js
tmorehouse Sep 21, 2019
42d203b
Update dropdown.js
tmorehouse Sep 21, 2019
056b0f2
Update dropdown.js
tmorehouse Sep 21, 2019
11eff1a
Update dropdown.js
tmorehouse Sep 21, 2019
7e676fd
Update dropdown.js
tmorehouse Sep 21, 2019
f135cf0
Update dropdown.js
tmorehouse Sep 21, 2019
b8e6578
Update dropdown.js
tmorehouse Sep 21, 2019
4072bf9
Update dropdown.js
tmorehouse Sep 21, 2019
45bb702
lint
tmorehouse Sep 21, 2019
a7e432c
Update dropdown.js
tmorehouse Sep 21, 2019
a9c8012
Update dropdown.js
tmorehouse Sep 21, 2019
dd9ba44
Update dropdown.js
tmorehouse Sep 21, 2019
2d1c465
Update dropdown.spec.js
tmorehouse Sep 21, 2019
84bea23
Update dropdown.spec.js
tmorehouse Sep 21, 2019
cc4ca68
Update dom.js
tmorehouse Sep 21, 2019
54a9af6
Update dom.spec.js
tmorehouse Sep 21, 2019
26f012d
Update dom.js
tmorehouse Sep 21, 2019
3413748
Update dom.js
tmorehouse Sep 21, 2019
046181b
Update dropdown.js
tmorehouse Sep 21, 2019
a2d4fc8
Update dropdown.js
tmorehouse Sep 21, 2019
5e20f0f
Update dropdown.js
tmorehouse Sep 21, 2019
ba792a4
Update dropdown.js
tmorehouse Sep 21, 2019
b7d42c3
Update dropdown.js
tmorehouse Sep 21, 2019
633f0e4
Update dropdown.js
tmorehouse Sep 21, 2019
d41c06a
Update dropdown.js
tmorehouse Sep 21, 2019
2c9a51f
Update dropdown.js
tmorehouse Sep 21, 2019
4d91f36
Update dropdown.js
tmorehouse Sep 21, 2019
587b29b
Update dropdown.js
tmorehouse Sep 21, 2019
e27fd71
Update dropdown.js
tmorehouse Sep 22, 2019
fec34e3
Update dropdown.js
tmorehouse Sep 22, 2019
281b9ac
Correct typos
jacobmllr95 Sep 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/components/dropdown/dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,10 @@ describe('dropdown', () => {
expect(document.activeElement).toBe($menu.element)

// Close menu by moving focus away from menu
const focusInEvt = new FocusEvent('focusin')
document.dispatchEvent(focusInEvt)
// which triggers a focusout event on menu
$menu.trigger('focusout', {
relatedTarget: document.body
})
await waitNT(wrapper.vm)
await waitRAF()
expect($dropdown.classes()).not.toContain('show')
Expand All @@ -494,9 +496,11 @@ describe('dropdown', () => {
expect($toggle.attributes('aria-expanded')).toEqual('true')
expect(document.activeElement).toBe($menu.element)

// Close menu by clicking outside of menu
const clickEvt = new MouseEvent('click')
document.dispatchEvent(clickEvt)
// Close menu by moving focus away from menu
// which triggers a focusout event on menu
$menu.trigger('focusout', {
relatedTarget: document.body
})
await waitNT(wrapper.vm)
await waitRAF()
expect($dropdown.classes()).not.toContain('show')
Expand Down
79 changes: 46 additions & 33 deletions src/mixins/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import Popper from 'popper.js'
import { BvEvent } from '../utils/bv-event.class'
import KeyCodes from '../utils/key-codes'
import warn from '../utils/warn'
import { closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
import { BvEvent } from '../utils/bv-event.class'
import { closest, contains, isVisible, requestAF, selectAll, eventOn, eventOff } from '../utils/dom'
import { isNull } from '../utils/inspect'
import clickOutMixin from './click-out'
import focusInMixin from './focus-in'
import idMixin from './id'

// Return an array of visible items
const filterVisibles = els => (els || []).filter(isVisible)

// Root dropdown event names
const ROOT_DROPDOWN_PREFIX = 'bv::dropdown::'
const ROOT_DROPDOWN_SHOWN = `${ROOT_DROPDOWN_PREFIX}shown`
const ROOT_DROPDOWN_HIDDEN = `${ROOT_DROPDOWN_PREFIX}hidden`

// Delay when loosing focus before closing menu (in ms)
const FOCUSOUT_DELAY = 100

// Dropdown item CSS selectors
const Selector = {
FORM_CHILD: '.dropdown form',
Expand Down Expand Up @@ -40,7 +47,7 @@ const AttachmentMap = {

// @vue/component
export default {
mixins: [clickOutMixin, focusInMixin],
mixins: [idMixin],
provide() {
return {
bvDropdown: this
Expand Down Expand Up @@ -136,7 +143,8 @@ export default {
cancelable: true,
vueTarget: this,
target: this.$refs.menu,
relatedTarget: null
relatedTarget: null,
componentId: this.safeId ? this.safeId() : this.id || null
})
this.emitEvent(bvEvt)
if (bvEvt.defaultPrevented) {
Expand Down Expand Up @@ -181,16 +189,13 @@ export default {
emitEvent(bvEvt) {
const type = bvEvt.type
this.$emit(type, bvEvt)
this.$root.$emit(`bv::dropdown::${type}`, bvEvt)
this.$root.$emit(`${ROOT_DROPDOWN_PREFIX}${type}`, bvEvt)
},
showMenu() {
if (this.disabled) {
/* istanbul ignore next */
return
}
// Ensure other menus are closed
this.$root.$emit('bv::dropdown::shown', this)

// Are we in a navbar ?
if (isNull(this.inNavbar) && this.isNav) {
// We should use an injection for this
Expand All @@ -213,6 +218,9 @@ export default {
}
}

// Ensure other menus are closed
this.$root.$emit(ROOT_DROPDOWN_SHOWN, this)

this.whileOpenListen(true)

// Wrap in nextTick to ensure menu is fully rendered/shown
Expand All @@ -225,7 +233,7 @@ export default {
},
hideMenu() {
this.whileOpenListen(false)
this.$root.$emit('bv::dropdown::hidden', this)
this.$root.$emit(ROOT_DROPDOWN_HIDDEN, this)
this.$emit('hidden')
this.removePopper()
},
Expand Down Expand Up @@ -263,19 +271,16 @@ export default {
}
return { ...popperConfig, ...(this.popperOpts || {}) }
},
whileOpenListen(open) {
whileOpenListen(isOpen) {
// turn listeners on/off while open
if (open) {
if (isOpen) {
// If another dropdown is opened
this.$root.$on('bv::dropdown::shown', this.rootCloseListener)
// Hide the dropdown when clicked outside
this.listenForClickOut = true
// Hide the dropdown when it loses focus
this.listenForFocusIn = true
this.$root.$on(ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
// Hide the menu when focus moves out
eventOn(this.$el, 'focusout', this.onFocusOut, { passive: true })
} else {
this.$root.$off('bv::dropdown::shown', this.rootCloseListener)
this.listenForClickOut = false
this.listenForFocusIn = false
this.$root.$off(ROOT_DROPDOWN_SHOWN, this.rootCloseListener)
eventOff(this.$el, 'focusout', this.onFocusOut, { passive: true })
}
},
rootCloseListener(vm) {
Expand Down Expand Up @@ -360,6 +365,7 @@ export default {
this.focusNext(evt, true)
}
},
// If uses presses ESC to close menu
onEsc(evt) {
if (this.visible) {
this.visible = false
Expand All @@ -369,18 +375,25 @@ export default {
this.$once('hidden', this.focusToggler)
}
},
// Document click out listener
clickOutHandler() {
if (this.visible) {
this.visible = false
}
},
// Document focusin listener
focusInHandler(evt) {
const target = evt.target
// If focus leaves dropdown, hide it
if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
this.visible = false
// Dropdown wrapper focusOut handler
onFocusOut(evt) {
// `relatedTarget` is the element gaining focus
const relatedTarget = evt.relatedTarget
// If focus moves outside the menu or toggler, then close menu
if (
this.visible &&
!contains(this.$refs.menu, relatedTarget) &&
!contains(this.toggler, relatedTarget)
) {
const doHide = () => {
this.visible = false
}
// When we are in a navbar (which has been responsively stacked), we
// delay the dropdown's closing so that the next element has a chance
// to have it's click handler fired (in case it's position moves on
// the screen do to a navbar menu above it collapsing)
// https://github.com/bootstrap-vue/bootstrap-vue/issues/4113
this.inNavbar ? setTimeout(doHide, FOCUSOUT_DELAY) : doHide()
}
},
// Keyboard nav
Expand Down
9 changes: 6 additions & 3 deletions src/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,16 @@ export const matches = (el, selector) => {
}

// Finds closest element matching selector. Returns `null` if not found
export const closest = (selector, root) => {
export const closest = (selector, root, includeRoot = false) => {
if (!isElement(root)) {
return null
}
const el = closestEl.call(root, selector)
// Emulate jQuery closest and return `null` if match is the passed in element (root)
return el === root ? null : el

// Native closest behaviour when `includeRoot` is truthy,
// else emulate jQuery closest and return `null` if match is
// the passed in root element when `includeRoot` is falsey
return includeRoot ? el : el === root ? null : el
}

// Returns true if the parent element contains the child element
Expand Down
2 changes: 2 additions & 0 deletions src/utils/dom.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ describe('utils/dom', () => {
expect(closest('div.baz', $btns.at(0).element)).toBeDefined()
expect(closest('div.baz', $btns.at(0).element)).toBe($baz.element)
expect(closest('div.nothere', $btns.at(0).element)).toBe(null)
expect(closest('div.baz', $baz.element)).toBe(null)
expect(closest('div.baz', $baz.element, true)).toBe($baz.element)

wrapper.destroy()
})
Expand Down