Skip to content

Commit bb50510

Browse files
committed
fix(b-toast): show and hide handling during active transitions [WIP]
1 parent 1e6b369 commit bb50510

File tree

7 files changed

+334
-230
lines changed

7 files changed

+334
-230
lines changed

src/components/modal/helpers/bv-modal.js

Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { NAME_MODAL, NAME_MSG_BOX } from '../../../constants/components'
33
import {
44
EVENT_NAME_HIDDEN,
55
EVENT_NAME_HIDE,
6+
EVENT_NAME_SHOW,
7+
EVENT_NAME_TOGGLE,
68
HOOK_EVENT_NAME_BEFORE_DESTROY,
79
HOOK_EVENT_NAME_DESTROYED
810
} from '../../../constants/events'
@@ -21,6 +23,7 @@ import {
2123
readonlyDescriptor
2224
} from '../../../utils/object'
2325
import { pluginFactory } from '../../../utils/plugins'
26+
import { pluckProps } from '../../../utils/props'
2427
import { warn, warnNotClient, warnNoPromiseSupport } from '../../../utils/warn'
2528
import { BModal, props as modalProps } from '../modal'
2629

@@ -51,16 +54,6 @@ const propsToSlots = {
5154

5255
// --- Helper methods ---
5356

54-
// Method to filter only recognized props that are not undefined
55-
const filterOptions = options => {
56-
return BASE_PROPS.reduce((memo, key) => {
57-
if (!isUndefined(options[key])) {
58-
memo[key] = options[key]
59-
}
60-
return memo
61-
}, {})
62-
}
63-
6457
// Method to install `$bvModal` VM injection
6558
const plugin = Vue => {
6659
// Create a private sub-component that extends BModal
@@ -116,7 +109,7 @@ const plugin = Vue => {
116109
parent: $parent,
117110
// Preset the prop values
118111
propsData: {
119-
...filterOptions(getComponentConfig(NAME_MODAL)),
112+
...pluckProps(BASE_PROPS, getComponentConfig(NAME_MODAL)),
120113
// Defaults that user can override
121114
hideHeaderClose: true,
122115
hideHeader: !(props.title || props.titleHtml),
@@ -166,7 +159,7 @@ const plugin = Vue => {
166159

167160
// Private utility method to open a user defined message box and returns a promise.
168161
// Not to be used directly by consumers, as this method may change calling syntax
169-
const makeMsgBox = ($parent, content, options = {}, resolver = null) => {
162+
const makeMsgBox = ($parent, content, props = {}, resolver = null) => {
170163
if (
171164
!content ||
172165
warnNoPromiseSupport(PROP_NAME) ||
@@ -176,7 +169,14 @@ const plugin = Vue => {
176169
/* istanbul ignore next */
177170
return
178171
}
179-
return asyncMsgBox($parent, { ...filterOptions(options), msgBoxContent: content }, resolver)
172+
return asyncMsgBox(
173+
$parent,
174+
{
175+
...pluckProps(BASE_PROPS, props),
176+
msgBoxContent: content
177+
},
178+
resolver
179+
)
180180
}
181181

182182
// BvModal instance class
@@ -193,17 +193,24 @@ const plugin = Vue => {
193193

194194
// --- Instance methods ---
195195

196-
// Show modal with the specified ID args are for future use
196+
// Show modal with the specified ID
197197
show(id, ...args) {
198-
if (id && this._root) {
199-
this._root.$emit(getRootActionEventName(NAME_MODAL, 'show'), id, ...args)
198+
if (id) {
199+
this._root.$emit(getRootActionEventName(NAME_MODAL, EVENT_NAME_SHOW), id, ...args)
200200
}
201201
}
202202

203-
// Hide modal with the specified ID args are for future use
203+
// Hide modal with the specified ID
204204
hide(id, ...args) {
205-
if (id && this._root) {
206-
this._root.$emit(getRootActionEventName(NAME_MODAL, 'hide'), id, ...args)
205+
if (id) {
206+
this._root.$emit(getRootActionEventName(NAME_MODAL, EVENT_NAME_HIDE), id, ...args)
207+
}
208+
}
209+
210+
// Toggle modal with the specified ID
211+
toggle(id, ...args) {
212+
if (id) {
213+
this._root.$emit(getRootActionEventName(NAME_MODAL, EVENT_NAME_TOGGLE), id, ...args)
207214
}
208215
}
209216

@@ -212,38 +219,44 @@ const plugin = Vue => {
212219
// should have a Polyfill loaded (which they need anyways for IE 11 support)
213220

214221
// Open a message box with OK button only and returns a promise
215-
msgBoxOk(message, options = {}) {
216-
// Pick the modal props we support from options
217-
const props = {
218-
...options,
219-
// Add in overrides and our content prop
220-
okOnly: true,
221-
okDisabled: false,
222-
hideFooter: false,
223-
msgBoxContent: message
224-
}
225-
return makeMsgBox(this._vm, message, props, () => {
226-
// Always resolve to true for OK
227-
return true
228-
})
222+
msgBoxOk(message, props = {}) {
223+
return makeMsgBox(
224+
this._vm,
225+
message,
226+
{
227+
...props,
228+
// Add in overrides and our content prop
229+
okOnly: true,
230+
okDisabled: false,
231+
hideFooter: false,
232+
msgBoxContent: message
233+
},
234+
() => {
235+
// Always resolve to true for OK
236+
return true
237+
}
238+
)
229239
}
230240

231241
// Open a message box modal with OK and CANCEL buttons
232242
// and returns a promise
233-
msgBoxConfirm(message, options = {}) {
234-
// Set the modal props we support from options
235-
const props = {
236-
...options,
237-
// Add in overrides and our content prop
238-
okOnly: false,
239-
okDisabled: false,
240-
cancelDisabled: false,
241-
hideFooter: false
242-
}
243-
return makeMsgBox(this._vm, message, props, bvModalEvent => {
244-
const trigger = bvModalEvent.trigger
245-
return trigger === 'ok' ? true : trigger === 'cancel' ? false : null
246-
})
243+
msgBoxConfirm(message, props = {}) {
244+
return makeMsgBox(
245+
this._vm,
246+
message,
247+
{
248+
...props,
249+
// Add in overrides and our content prop
250+
okOnly: false,
251+
okDisabled: false,
252+
cancelDisabled: false,
253+
hideFooter: false
254+
},
255+
bvModalEvent => {
256+
const trigger = bvModalEvent.trigger
257+
return trigger === 'ok' ? true : trigger === 'cancel' ? false : null
258+
}
259+
)
247260
}
248261
}
249262

src/components/modal/modal.js

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ export const BModal = /*#__PURE__*/ Vue.extend({
369369
// Listen for `bv:modal::show events`, and close ourselves if the
370370
// opening modal not us
371371
this.listenOnRoot(getRootEventName(NAME_MODAL, EVENT_NAME_SHOW), this.modalListener)
372-
// Initially show modal?
373-
if (this[MODEL_PROP_NAME] === true) {
372+
// Initially show modal
373+
if (this[MODEL_PROP_NAME]) {
374374
this.$nextTick(this.show)
375375
}
376376
},
@@ -384,6 +384,41 @@ export const BModal = /*#__PURE__*/ Vue.extend({
384384
}
385385
},
386386
methods: {
387+
// Private method to get the current document active element
388+
getActiveElement() {
389+
// Returning focus to `document.body` may cause unwanted scrolls,
390+
// so we exclude setting focus on body
391+
const activeElement = getActiveElement(IS_BROWSER ? [document.body] : [])
392+
// Preset the fallback return focus value if it is not set
393+
// `document.activeElement` should be the trigger element that was clicked or
394+
// in the case of using the v-model, which ever element has current focus
395+
// Will be overridden by some commands such as toggle, etc.
396+
// Note: On IE 11, `document.activeElement` may be `null`
397+
// So we test it for truthiness first
398+
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3206
399+
return activeElement && activeElement.focus ? activeElement : null
400+
},
401+
buildEvent(type, options = {}) {
402+
return new BvModalEvent(type, {
403+
// Default options
404+
cancelable: false,
405+
target: this.$refs.modal || this.$el || null,
406+
relatedTarget: null,
407+
trigger: null,
408+
// Supplied options
409+
...options,
410+
// Options that can't be overridden
411+
vueTarget: this,
412+
componentId: this.modalId
413+
})
414+
},
415+
emitEvent(bvEvent) {
416+
const { type } = bvEvent
417+
// We emit on `$root` first in case a global listener wants to cancel
418+
// the event first before the instance emits its event
419+
this.emitOnRoot(getRootEventName(NAME_MODAL, type), bvEvent, bvEvent.componentId)
420+
this.$emit(type, bvEvent)
421+
},
387422
setObserver(on = false) {
388423
this.$_observer && this.$_observer.disconnect()
389424
this.$_observer = null
@@ -395,32 +430,16 @@ export const BModal = /*#__PURE__*/ Vue.extend({
395430
)
396431
}
397432
},
398-
// Private method to update the v-model
433+
// Private method to update the `v-model`
399434
updateModel(value) {
400435
if (value !== this[MODEL_PROP_NAME]) {
401436
this.$emit(MODEL_EVENT_NAME, value)
402437
}
403438
},
404-
// Private method to create a BvModalEvent object
405-
buildEvent(type, options = {}) {
406-
return new BvModalEvent(type, {
407-
// Default options
408-
cancelable: false,
409-
target: this.$refs.modal || this.$el || null,
410-
relatedTarget: null,
411-
trigger: null,
412-
// Supplied options
413-
...options,
414-
// Options that can't be overridden
415-
vueTarget: this,
416-
componentId: this.modalId
417-
})
418-
},
419-
// Public method to show modal
420439
show() {
440+
// If already open, or in the process of opening, do nothing
441+
/* istanbul ignore next */
421442
if (this.isVisible || this.isOpening) {
422-
// If already open, or in the process of opening, do nothing
423-
/* istanbul ignore next */
424443
return
425444
}
426445
/* istanbul ignore next */
@@ -448,10 +467,10 @@ export const BModal = /*#__PURE__*/ Vue.extend({
448467
// Show the modal
449468
this.doShow()
450469
},
451-
// Public method to hide modal
452470
hide(trigger = '') {
471+
// If already closed, or in the process of closing, do nothing
472+
/* istanbul ignore next */
453473
if (!this.isVisible || this.isClosing) {
454-
/* istanbul ignore next */
455474
return
456475
}
457476
this.isClosing = true
@@ -482,7 +501,6 @@ export const BModal = /*#__PURE__*/ Vue.extend({
482501
// Update the v-model
483502
this.updateModel(false)
484503
},
485-
// Public method to toggle modal visibility
486504
toggle(triggerEl) {
487505
if (triggerEl) {
488506
this.$_returnFocus = triggerEl
@@ -493,20 +511,6 @@ export const BModal = /*#__PURE__*/ Vue.extend({
493511
this.show()
494512
}
495513
},
496-
// Private method to get the current document active element
497-
getActiveElement() {
498-
// Returning focus to `document.body` may cause unwanted scrolls,
499-
// so we exclude setting focus on body
500-
const activeElement = getActiveElement(IS_BROWSER ? [document.body] : [])
501-
// Preset the fallback return focus value if it is not set
502-
// `document.activeElement` should be the trigger element that was clicked or
503-
// in the case of using the v-model, which ever element has current focus
504-
// Will be overridden by some commands such as toggle, etc.
505-
// Note: On IE 11, `document.activeElement` may be `null`
506-
// So we test it for truthiness first
507-
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3206
508-
return activeElement && activeElement.focus ? activeElement : null
509-
},
510514
// Private method to finish showing modal
511515
doShow() {
512516
/* istanbul ignore next: commenting out for now until we can test stacking */
@@ -588,13 +592,6 @@ export const BModal = /*#__PURE__*/ Vue.extend({
588592
this.emitEvent(this.buildEvent(EVENT_NAME_HIDDEN))
589593
})
590594
},
591-
emitEvent(bvEvent) {
592-
const { type } = bvEvent
593-
// We emit on `$root` first in case a global listener wants to cancel
594-
// the event first before the instance emits its event
595-
this.emitOnRoot(getRootEventName(NAME_MODAL, type), bvEvent, bvEvent.componentId)
596-
this.$emit(type, bvEvent)
597-
},
598595
// UI event handlers
599596
onDialogMousedown() {
600597
// Watch to see if the matching mouseup event occurs outside the dialog

0 commit comments

Comments
 (0)