Skip to content

fix(toaster): let on-demand toasts know the toaster has been destroyed #3130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f7a393c
Update toaster.js
tmorehouse Apr 17, 2019
9aa78dc
Update bv-toast.js
tmorehouse Apr 17, 2019
15cafae
Update toast.js
tmorehouse Apr 17, 2019
f85ee0f
Update bv-toast.js
tmorehouse Apr 17, 2019
a30f1d6
Update toast.js
tmorehouse Apr 17, 2019
1df9517
Update toaster.js
tmorehouse Apr 17, 2019
fb01a86
Update bv-toast.js
tmorehouse Apr 17, 2019
b01bfa8
Update toast.js
tmorehouse Apr 17, 2019
d166e74
Merge branch 'dev' into tmorehouse/toaster-destroy
tmorehouse Apr 17, 2019
43d77fb
Update toaster.js
tmorehouse Apr 18, 2019
7739cc1
Update toaster.js
tmorehouse Apr 18, 2019
90a78df
Update toaster.js
tmorehouse Apr 18, 2019
2c83d93
Update toaster.js
tmorehouse Apr 18, 2019
560293e
Update toaster.js
tmorehouse Apr 18, 2019
5754675
Update toaster.js
tmorehouse Apr 18, 2019
9eb7672
Update toast.js
tmorehouse Apr 18, 2019
da8c82c
lint
tmorehouse Apr 18, 2019
a91f4b9
Update toaster.js
tmorehouse Apr 18, 2019
162a301
Update README.md
tmorehouse Apr 18, 2019
b569b9c
Update toast.js
tmorehouse Apr 18, 2019
18c0612
Update _variables.scss
tmorehouse Apr 18, 2019
8c5741b
Update _toaster.scss
tmorehouse Apr 18, 2019
4508516
Update _variables.scss
tmorehouse Apr 18, 2019
a2b6e7f
Update _toaster.scss
tmorehouse Apr 18, 2019
03d0b26
Update _variables.scss
tmorehouse Apr 18, 2019
201f73f
Update _toaster.scss
tmorehouse Apr 18, 2019
ea768de
Update _variables.scss
tmorehouse Apr 18, 2019
69ae759
Update package.json
tmorehouse Apr 18, 2019
f46d8b2
Update _variables.scss
tmorehouse Apr 18, 2019
55947a6
Update _toaster.scss
tmorehouse Apr 18, 2019
2f14147
Update _toaster.scss
tmorehouse Apr 18, 2019
cc0663c
Update README.md
tmorehouse Apr 18, 2019
a316724
Update README.md
tmorehouse Apr 18, 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
3 changes: 3 additions & 0 deletions src/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ $b-toaster-offset-top: 0.5rem !default;
$b-toaster-offset-bottom: $b-toaster-offset-top !default;
$b-toaster-offset-left: $b-toaster-offset-top !default;
$b-toaster-offset-right: $b-toaster-offset-top !default;
$b-toaster-max-width: $toast-max-width !default;
// Make toasts fit within small screens
$b-toaster-min-width: calc(#{min(320px, $toast-max-width)} - #{$b-toaster-offset-left + $b-toaster-offset-right}) !default;

$b-toast-bg-level: $alert-bg-level !default;
$b-toast-border-level: $alert-border-level !default;
Expand Down
18 changes: 14 additions & 4 deletions src/components/toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ contain minimal, to-the-point, non-interactive content.
<p class="alert alert-warning mb-0" role="alert">
<strong>BETA warning</strong><br>
Toasts are in their preliminary stages of being developed,
and usage is subject to change in future releases.
and usage and custom CSS is subject to change in future releases.
</p>

## Overview
Expand Down Expand Up @@ -185,9 +185,15 @@ SCSS):

<script>
export default {
data() {
return {
counter: 0
}
},
methods: {
toast(toaster) {
this.$bvToast.toast('Toast body content', {
this.counter++
this.$bvToast.toast(`Toast ${this.counter} body content`, {
title: `Toaster ${toaster}`,
toaster: toaster,
solid: true
Expand All @@ -205,11 +211,15 @@ document, stacked and not positioned (appended to `<body>` inside a `<b-toaster>
and ID set to the toaster target name). The only default styling the toaster will have is
`position: fixed;`, a `max-width` and a `z-index` of `1100`.

Avoid using both `b-toaster-top-left` and `b-toaster-top-right`, or `b-toaster-bottom-left` and
`b-toaster-bottom-right`, at the same time in your app as notifications could be obscured on small
screens (i.e. `xs`).

### Prepend and append

Toasts default to prepending themselves to the top of the toasts shown in the specified toaster in
the order they were created. To append new toasts to the bottom, set the `append-toast` prop to
`true`
`true`.

### Auto-hide

Expand All @@ -220,7 +230,7 @@ to `true`.
### Toast roles

Toasts are rendered with a default `role` attribute of `'alert'` and `aria-live` attribute of
`'assertive'`. for toasts that are meant for a casual notification, set the `is-status` prop to
`'assertive'`. For toasts that are meant for a casual notification, set the `is-status` prop to
`true`, which will change the `role` and `aria-live` attributes to `'status'` and `'polite'`
respectively.

Expand Down
20 changes: 12 additions & 8 deletions src/components/toast/_toaster.scss
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
// --- <b-toaster> custom SCSS ---

// Configured "toasters".
// Configured "toasters":
// The following are columnar toasters (vertically stacked)
// and fixed within the viewport
// b-toaster-top-right
// b-toaster-top-left
// b-toaster-bottom-right
// b-toaster-bottom-left
//
// Note: This may change to using a flex layout
//
// NOTE:
// This SCSS is prelimiary, and may change in the future.
// We may add an inner wrapper inside .b-toast (a .b-toaster-slot)
// To allow for better customization of positional styling.

.b-toaster {
position: fixed;
max-width: $toast-max-width;
min-width: $b-toaster-min-width;
max-width: $b-toaster-max-width;
overflow: visible;
z-index: $b-toaster-zindex;

// Ensure that an empty toaster isn't occupying any space
// and obscuring access to underlying elements
&:empty {
padding: 0 0;
margin: 0 0;
display: none;
}

&.b-toaster-top-right,
Expand All @@ -33,14 +39,12 @@
}

&.b-toaster-top-right,
&.b-toaster-bottom-right {
&.b-toaster-bottom-right {
right: $b-toaster-offset-right;
min-width: calc(#{$toast-max-width} - #{$b-toaster-offset-right * 2});
}

&.b-toaster-top-left,
&.b-toaster-bottom-left {
&.b-toaster-bottom-left {
left: $b-toaster-offset-left;
min-width: calc(#{$toast-max-width} - #{$b-toaster-offset-left * 2});
}
}
6 changes: 6 additions & 0 deletions src/components/toast/helpers/bv-toast.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ const BToastPop = Vue.extend({
this.$parent.$once('hook:destroyed', handleDestroy)
// Self destruct after hidden
this.$once('hidden', handleDestroy)
// Self destruct when toaster is destroyed
this.listenOnRoot('bv::toaster::destroyed', toaster => {
if (toaster === self.toaster) {
handleDestroy()
}
})
}
})

Expand Down
2 changes: 1 addition & 1 deletion src/components/toast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"events": [
{
"event": "change",
"description": "Toast visibility state. Updates the v-modal.",
"description": "Toast visibility state. Used to update the v-model.",
"args": [
{
"arg": "visible",
Expand Down
32 changes: 27 additions & 5 deletions src/components/toast/toast.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Vue from '../../utils/vue'
import { Portal } from 'portal-vue'
import { Portal, Wormhole } from 'portal-vue'
import BvEvent from '../../utils/bv-event.class'
import { getComponentConfig } from '../../utils/config'
import { getById, requestAF } from '../../utils/dom'
Expand Down Expand Up @@ -171,9 +171,20 @@ export default Vue.extend({
newVal ? this.show() : this.hide()
},
localShow(newVal) {
if (newVal !== this.show) {
if (newVal !== this.visible) {
this.$emit('change', newVal)
}
},
toaster(newVal) {
// If toaster target changed, make sure toaster exists
this.$nextTick(() => this.ensureToaster)
},
static(newVal) {
// If static changes to true, and the toast is showing,
// ensure the toaster target exists
if (newVal && this.localShow) {
this.ensureToaster()
}
}
},
mounted() {
Expand All @@ -185,16 +196,24 @@ export default Vue.extend({
})
}
})
this.listenOnRoot('bv::show:toast', id => {
// Listen for global $root show events
this.listenOnRoot('bv::show::toast', id => {
if (id === this.id) {
this.show()
}
})
this.listenOnRoot('bv::hide:toast', id => {
// Listen for global $root hide events
this.listenOnRoot('bv::hide::toast', id => {
if (!id || id === this.id) {
this.hide()
}
})
// Make sure we hide when toaster is destroyed
this.listenOnRoot('bv::toaster::destroyed', toaster => {
if (toaster === this.toaster) {
this.hide()
}
})
},
beforeDestroy() {
this.clearDismissTimer()
Expand Down Expand Up @@ -234,7 +253,10 @@ export default Vue.extend({
this.$emit(type, bvEvt)
},
ensureToaster() {
if (!getById(this.toaster)) {
if (this.static) {
return
}
if (!getById(this.toaster) && !Wormhole.hasTarget(this.toaster)) {
const div = document.createElement('div')
document.body.append(div)
const toaster = new BToaster({
Expand Down
49 changes: 41 additions & 8 deletions src/components/toast/toaster.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vue from '../../utils/vue'
import { PortalTarget, Wormhole } from 'portal-vue'
import warn from '../../utils/warn'
import { getById } from '../../utils/dom'
import { getById, removeClass, requestAF } from '../../utils/dom'

/* istanbul ignore file: for now until ready for testing */

Expand Down Expand Up @@ -37,9 +37,35 @@ export const props = {

// @vue/component
export const DefaultTransition = Vue.extend({
functional: true,
render(h, { children }) {
return h('transition-group', { props: { tag: 'div', name: 'b-toaster' } }, children)
// functional: true,
// render(h, { children }) {
// return h('transition-group', { props: { tag: 'div', name: 'b-toaster' } }, children)
data() {
return {
// Transition classes base name
name: 'b-toaster'
}
},
methods: {
onAfterEnter(el) {
// Handle bug where enter-to class is not removed.
// Bug is related to portal-vue and transition-groups.
requestAF(() => {
removeClass(el, `${this.name}-enter-to`)
// The *-move class is also stuck on elements that moved,
// but there are no javascript hooks to handle after move.
})
}
},
render(h) {
return h(
'transition-group',
{
props: { tag: 'div', name: this.name },
on: { afterEnter: this.onAfterEnter }
},
this.$slots.default
)
}
})

Expand All @@ -50,15 +76,22 @@ export default Vue.extend({
data() {
return {
// We don't render on SSR or if a an existing target found
doRender: false
doRender: false,
dead: false
}
},
beforeMount() {
/* istanbul ignore if */
if (getById(this.name) || Wormhole.targets[this.name]) {
warn(`b-toaster: A <portal-target> name '${this.name}' already exists in the document.`)
if (getById(this.name) || Wormhole.hasTarget(this.name)) {
warn(`b-toaster: A <portal-target> with name '${this.name}' already exists in the document.`)
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.name)
})
}
},
destroyed() {
Expand All @@ -68,7 +101,7 @@ export default Vue.extend({
}
},
render(h) {
let $target = h('div', { class: 'd-none' })
let $target = h('div', { class: ['d-none', { 'b-dead-toaster': this.dead }] })
if (this.doRender) {
$target = h(PortalTarget, {
staticClass: 'b-toaster',
Expand Down