bootstrap-vue
Version:
With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens
138 lines (133 loc) • 3.65 kB
JavaScript
import { PortalTarget, Wormhole } from 'portal-vue'
import Vue from '../../vue'
import { NAME_TOASTER } from '../../constants/components'
import { getComponentConfig } from '../../utils/config'
import { removeClass, requestAF } from '../../utils/dom'
import { warn } from '../../utils/warn'
// --- Props ---
export const props = {
name: {
type: String,
required: true
},
ariaLive: {
type: String,
default: () => getComponentConfig(NAME_TOASTER, 'ariaLive')
},
ariaAtomic: {
type: String,
default: () => getComponentConfig(NAME_TOASTER, 'ariaAtomic') // Allowed: 'true' or 'false' or null
},
role: {
// Aria role
type: String,
default: () => getComponentConfig(NAME_TOASTER, 'role')
}
/*
transition: {
type: [Boolean, String, Object],
default: false
}
*/
}
// @vue/component
export const DefaultTransition = /*#__PURE__*/ Vue.extend({
data() {
return {
// Transition classes base name
name: 'b-toaster'
}
},
methods: {
onAfterEnter(el) {
// Work around a Vue.js bug where `*-enter-to` class is not removed
// See: https://github.com/vuejs/vue/pull/7901
// The `*-move` class is also stuck on elements that moved,
// but there are no JavaScript hooks to handle after move
// See: https://github.com/vuejs/vue/pull/7906
requestAF(() => {
removeClass(el, `${this.name}-enter-to`)
})
}
},
render(h) {
return h(
'transition-group',
{
props: { tag: 'div', name: this.name },
on: { afterEnter: this.onAfterEnter }
},
this.$slots.default
)
}
})
// @vue/component
export const BToaster = /*#__PURE__*/ Vue.extend({
name: NAME_TOASTER,
props,
data() {
return {
// We don't render on SSR or if a an existing target found
doRender: false,
dead: false,
// Toaster names cannot change once created
staticName: this.name
}
},
beforeMount() {
this.staticName = this.name
/* istanbul ignore if */
if (Wormhole.hasTarget(this.staticName)) {
warn(
`A "<portal-target>" with name "${this.name}" already exists in the document.`,
NAME_TOASTER
)
this.dead = true
} else {
this.doRender = true
this.$once('hook:beforeDestroy', () => {
// Let toasts made with `this.$bvToast.toast()` know that this toaster
// is being destroyed and should should also destroy/hide themselves
this.$root.$emit('bv::toaster::destroyed', this.staticName)
})
}
},
destroyed() {
// Remove from DOM if needed
/* istanbul ignore next: difficult to test */
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
}
},
render(h) {
let $toaster = h('div', { class: ['d-none', { 'b-dead-toaster': this.dead }] })
if (this.doRender) {
const $target = h(PortalTarget, {
staticClass: 'b-toaster-slot',
props: {
name: this.staticName,
multiple: true,
tag: 'div',
slim: false,
// transition: this.transition || DefaultTransition
transition: DefaultTransition
}
})
$toaster = h(
'div',
{
staticClass: 'b-toaster',
class: [this.staticName],
attrs: {
id: this.staticName,
role: this.role || null, // Fallback to null to make sure attribute doesn't exist
'aria-live': this.ariaLive,
'aria-atomic': this.ariaAtomic
}
},
[$target]
)
}
return $toaster
}
})