diff --git a/src/components/dropdown/package.json b/src/components/dropdown/package.json index 34d5c55cb3d..93329b09fff 100755 --- a/src/components/dropdown/package.json +++ b/src/components/dropdown/package.json @@ -11,23 +11,47 @@ "bDropdownDivider" ], "events": [ + { + "event": "show", + "description": "Emitted just before dropdown is shown. Cancelable.", + "args": [ + { + "arg": "bvEvt", + "description": "BvEvent object. Call bvEvt.preventDefault() to cancel show." + } + ] + }, { "event": "shown", - "description": "Emitted When dropdown is shown" + "description": "Emitted when dropdown is shown." + }, + { + "event": "hide", + "description": "Emitted just before dropdown is hidden. Cancelable.", + "args": [ + { + "arg": "bvEvt", + "description": "BvEvent object. Call bvEvt.preventDefault() to cancel hide." + } + ] }, { "event": "hidden", - "description": "Emitted When dropdown is hidden" + "description": "Emitted when dropdown is hidden." + }, + { + "event": "toggle", + "description": "Emitted when toggle button is clicked." }, { "event": "click", - "description": "Emitted when split button clicked in split mode." + "description": "Emitted when split button is clicked in split mode." } ], "slots": [ { "name": "button-content", - "description": "Can be used to implement custom text with icons and more styling" + "description": "Can be used to implement custom text with icons and more styling." }, { "name": "text", diff --git a/src/mixins/dropdown.js b/src/mixins/dropdown.js index 29e39d7209c..7001d2cc208 100644 --- a/src/mixins/dropdown.js +++ b/src/mixins/dropdown.js @@ -4,6 +4,7 @@ import listenOnRootMixin from './listen-on-root' import { from as arrayFrom } from '../utils/array' import { assign } from '../utils/object' import KeyCodes from '../utils/key-codes' +import BvEvent from '../utils/bv-event.class' import warn from '../utils/warn' import { isVisible, closest, selectAll, getAttr, eventOn, eventOff } from '../utils/dom' @@ -68,7 +69,8 @@ export default { data () { return { visible: false, - inNavbar: null + inNavbar: null, + visibleChangePrevented: false } }, created () { @@ -97,19 +99,36 @@ export default { this.removePopper() }, watch: { - visible (state, old) { - if (state === old) { - // Avoid duplicated emits + visible (newValue, oldValue) { + if (this.visibleChangePrevented) { + this.visibleChangePrevented = false return } - if (state) { - this.showMenu() - } else { - this.hideMenu() + + if (newValue !== oldValue) { + const evtName = newValue ? 'show' : 'hide' + let bvEvt = new BvEvent(evtName, { + cancelable: true, + vueTarget: this, + target: this.$refs.menu, + relatedTarget: null + }) + this.emitEvent(bvEvt) + if (bvEvt.defaultPrevented) { + // Reset value and exit if canceled + this.visibleChangePrevented = true + this.visible = oldValue + return + } + if (evtName === 'show') { + this.showMenu() + } else { + this.hideMenu() + } } }, - disabled (state, old) { - if (state !== old && state && this.visible) { + disabled (newValue, oldValue) { + if (newValue !== oldValue && newValue && this.visible) { // Hide dropdown if disabled changes to true this.visible = false } @@ -121,12 +140,16 @@ export default { } }, methods: { + // Event emitter + emitEvent (bvEvt) { + const type = bvEvt.type + this.$emit(type, bvEvt) + this.emitOnRoot(`bv::dropdown::${type}`, bvEvt) + }, showMenu () { if (this.disabled) { return } - // TODO: move emit show to visible watcher, to allow cancelling of show - this.$emit('show') // Ensure other menus are closed this.emitOnRoot('bv::dropdown::shown', this) @@ -157,8 +180,6 @@ export default { this.$nextTick(this.focusFirstItem) }, hideMenu () { - // TODO: move emit hide to visible watcher, to allow cancelling of hide - this.$emit('hide') this.setTouchStart(false) this.emitOnRoot('bv::dropdown::hidden', this) this.$emit('hidden') @@ -258,12 +279,17 @@ export default { // We only toggle on Click, Enter, Space, and Arrow Down return } - evt.preventDefault() - evt.stopPropagation() if (this.disabled) { this.visible = false return } + this.$emit('toggle', evt) + if (evt.defaultPrevented) { + // Exit if canceled + return + } + evt.preventDefault() + evt.stopPropagation() // Toggle visibility this.visible = !this.visible },