toaster.js
3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { PortalTarget, Wormhole } from 'portal-vue'
import { extend } from '../../vue'
import { NAME_TOASTER } from '../../constants/components'
import { EVENT_NAME_DESTROYED } from '../../constants/events'
import { PROP_TYPE_STRING } from '../../constants/props'
import { removeClass, requestAF } from '../../utils/dom'
import { getRootEventName } from '../../utils/events'
import { makeProp, makePropsConfigurable } from '../../utils/props'
import { warn } from '../../utils/warn'
import { listenOnRootMixin } from '../../mixins/listen-on-root'
import { normalizeSlotMixin } from '../../mixins/normalize-slot'
// --- Helper components ---
// @vue/component
export const DefaultTransition = /*#__PURE__*/ extend({
mixins: [normalizeSlotMixin],
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.normalizeSlot()
)
}
})
// --- Props ---
export const props = makePropsConfigurable(
{
// Allowed: 'true' or 'false' or `null`
ariaAtomic: makeProp(PROP_TYPE_STRING),
ariaLive: makeProp(PROP_TYPE_STRING),
name: makeProp(PROP_TYPE_STRING, undefined, true), // Required
// Aria role
role: makeProp(PROP_TYPE_STRING)
},
NAME_TOASTER
)
// --- Main component ---
// @vue/component
export const BToaster = /*#__PURE__*/ extend({
name: NAME_TOASTER,
mixins: [listenOnRootMixin],
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() {
const { name } = this
this.staticName = name
/* istanbul ignore if */
if (Wormhole.hasTarget(name)) {
warn(`A "<portal-target>" with name "${name}" already exists in the document.`, NAME_TOASTER)
this.dead = true
} else {
this.doRender = true
}
},
beforeDestroy() {
// Let toasts made with `this.$bvToast.toast()` know that this toaster
// is being destroyed and should should also destroy/hide themselves
if (this.doRender) {
this.emitOnRoot(getRootEventName(NAME_TOASTER, EVENT_NAME_DESTROYED), this.name)
}
},
destroyed() {
// Remove from DOM if needed
const { $el } = this
/* istanbul ignore next: difficult to test */
if ($el && $el.parentNode) {
$el.parentNode.removeChild($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,
// Fallback to null to make sure attribute doesn't exist
role: this.role || null,
'aria-live': this.ariaLive,
'aria-atomic': this.ariaAtomic
}
},
[$target]
)
}
return $toaster
}
})