function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import Vue from '../../utils/vue'; import { Portal, Wormhole } from 'portal-vue'; import BVTransition from '../../utils/bv-transition'; import { BvEvent } from '../../utils/bv-event.class'; import { getComponentConfig } from '../../utils/config'; import { requestAF } from '../../utils/dom'; import { EVENT_OPTIONS_NO_CAPTURE, eventOnOff } from '../../utils/events'; import { toInteger } from '../../utils/number'; import idMixin from '../../mixins/id'; import listenOnRootMixin from '../../mixins/listen-on-root'; import normalizeSlotMixin from '../../mixins/normalize-slot'; import scopedStyleAttrsMixin from '../../mixins/scoped-style-attrs'; import { BToaster } from './toaster'; import { BButtonClose } from '../button/button-close'; import { BLink } from '../link/link'; // --- Constants --- var NAME = 'BToast'; var MIN_DURATION = 1000; // --- Props --- export var props = { id: { // Even though the ID prop is provided by idMixin, we // add it here for $bvToast props filtering type: String, default: null }, title: { type: String, default: null }, toaster: { type: String, default: function _default() { return getComponentConfig(NAME, 'toaster'); } }, visible: { type: Boolean, default: false }, variant: { type: String, default: function _default() { return getComponentConfig(NAME, 'variant'); } }, isStatus: { // Switches role to 'status' and aria-live to 'polite' type: Boolean, default: false }, appendToast: { type: Boolean, default: false }, noAutoHide: { type: Boolean, default: false }, autoHideDelay: { type: [Number, String], default: function _default() { return getComponentConfig(NAME, 'autoHideDelay'); } }, noCloseButton: { type: Boolean, default: false }, noFade: { type: Boolean, default: false }, noHoverPause: { type: Boolean, default: false }, solid: { type: Boolean, default: false }, toastClass: { type: [String, Object, Array], default: function _default() { return getComponentConfig(NAME, 'toastClass'); } }, headerClass: { type: [String, Object, Array], default: function _default() { return getComponentConfig(NAME, 'headerClass'); } }, bodyClass: { type: [String, Object, Array], default: function _default() { return getComponentConfig(NAME, 'bodyClass'); } }, href: { type: String, default: null }, to: { type: [String, Object], default: null }, static: { // Render the toast in place, rather than in a portal-target type: Boolean, default: false } }; // @vue/component export var BToast = /*#__PURE__*/ Vue.extend({ name: NAME, mixins: [idMixin, listenOnRootMixin, normalizeSlotMixin, scopedStyleAttrsMixin], inheritAttrs: false, model: { prop: 'visible', event: 'change' }, props: props, data: function data() { return { isMounted: false, doRender: false, localShow: false, isTransitioning: false, isHiding: false, order: 0, timer: null, dismissStarted: 0, resumeDismiss: 0 }; }, computed: { bToastClasses: function bToastClasses() { return _defineProperty({ 'b-toast-solid': this.solid, 'b-toast-append': this.appendToast, 'b-toast-prepend': !this.appendToast }, "b-toast-".concat(this.variant), this.variant); }, slotScope: function slotScope() { return { hide: this.hide }; }, computedDuration: function computedDuration() { // Minimum supported duration is 1 second return Math.max(toInteger(this.autoHideDelay) || 0, MIN_DURATION); }, computedToaster: function computedToaster() { return String(this.toaster); }, transitionHandlers: function transitionHandlers() { return { beforeEnter: this.onBeforeEnter, afterEnter: this.onAfterEnter, beforeLeave: this.onBeforeLeave, afterLeave: this.onAfterLeave }; } }, watch: { visible: function visible(newVal) { newVal ? this.show() : this.hide(); }, localShow: function localShow(newVal) { if (newVal !== this.visible) { this.$emit('change', newVal); } }, toaster: function toaster() /* istanbul ignore next */ { // If toaster target changed, make sure toaster exists this.$nextTick(this.ensureToaster); }, static: function _static(newVal) /* istanbul ignore next */ { // If static changes to true, and the toast is showing, // ensure the toaster target exists if (newVal && this.localShow) { this.ensureToaster(); } } }, mounted: function mounted() { var _this = this; this.isMounted = true; this.$nextTick(function () { if (_this.visible) { requestAF(function () { _this.show(); }); } }); // Listen for global $root show events this.listenOnRoot('bv::show::toast', function (id) { if (id === _this.safeId()) { _this.show(); } }); // Listen for global $root hide events this.listenOnRoot('bv::hide::toast', function (id) { if (!id || id === _this.safeId()) { _this.hide(); } }); // Make sure we hide when toaster is destroyed /* istanbul ignore next: difficult to test */ this.listenOnRoot('bv::toaster::destroyed', function (toaster) { /* istanbul ignore next */ if (toaster === _this.computedToaster) { /* istanbul ignore next */ _this.hide(); } }); }, beforeDestroy: function beforeDestroy() { this.clearDismissTimer(); }, methods: { show: function show() { var _this2 = this; if (!this.localShow) { this.ensureToaster(); var showEvt = this.buildEvent('show'); this.emitEvent(showEvt); this.dismissStarted = this.resumeDismiss = 0; this.order = Date.now() * (this.appendToast ? 1 : -1); this.isHiding = false; this.doRender = true; this.$nextTick(function () { // We show the toast after we have rendered the portal and b-toast wrapper // so that screen readers will properly announce the toast requestAF(function () { _this2.localShow = true; }); }); } }, hide: function hide() { var _this3 = this; if (this.localShow) { var hideEvt = this.buildEvent('hide'); this.emitEvent(hideEvt); this.setHoverHandler(false); this.dismissStarted = this.resumeDismiss = 0; this.clearDismissTimer(); this.isHiding = true; requestAF(function () { _this3.localShow = false; }); } }, buildEvent: function buildEvent(type) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new BvEvent(type, _objectSpread({ cancelable: false, target: this.$el || null, relatedTarget: null }, options, { vueTarget: this, componentId: this.safeId() })); }, emitEvent: function emitEvent(bvEvt) { var type = bvEvt.type; this.$root.$emit("bv::toast:".concat(type), bvEvt); this.$emit(type, bvEvt); }, ensureToaster: function ensureToaster() { if (this.static) { return; } if (!Wormhole.hasTarget(this.computedToaster)) { var div = document.createElement('div'); document.body.appendChild(div); var toaster = new BToaster({ parent: this.$root, propsData: { name: this.computedToaster } }); toaster.$mount(div); } }, startDismissTimer: function startDismissTimer() { this.clearDismissTimer(); if (!this.noAutoHide) { this.timer = setTimeout(this.hide, this.resumeDismiss || this.computedDuration); this.dismissStarted = Date.now(); this.resumeDismiss = 0; } }, clearDismissTimer: function clearDismissTimer() { clearTimeout(this.timer); this.timer = null; }, setHoverHandler: function setHoverHandler(on) { var el = this.$refs['b-toast']; eventOnOff(on, el, 'mouseenter', this.onPause, EVENT_OPTIONS_NO_CAPTURE); eventOnOff(on, el, 'mouseleave', this.onUnPause, EVENT_OPTIONS_NO_CAPTURE); }, onPause: function onPause() { // Determine time remaining, and then pause timer if (this.noAutoHide || this.noHoverPause || !this.timer || this.resumeDismiss) { return; } var passed = Date.now() - this.dismissStarted; if (passed > 0) { this.clearDismissTimer(); this.resumeDismiss = Math.max(this.computedDuration - passed, MIN_DURATION); } }, onUnPause: function onUnPause() { // Restart timer with max of time remaining or 1 second if (this.noAutoHide || this.noHoverPause || !this.resumeDismiss) { this.resumeDismiss = this.dismissStarted = 0; return; } this.startDismissTimer(); }, onLinkClick: function onLinkClick() { var _this4 = this; // We delay the close to allow time for the // browser to process the link click this.$nextTick(function () { requestAF(function () { _this4.hide(); }); }); }, onBeforeEnter: function onBeforeEnter() { this.isTransitioning = true; }, onAfterEnter: function onAfterEnter() { this.isTransitioning = false; var hiddenEvt = this.buildEvent('shown'); this.emitEvent(hiddenEvt); this.startDismissTimer(); this.setHoverHandler(true); }, onBeforeLeave: function onBeforeLeave() { this.isTransitioning = true; }, onAfterLeave: function onAfterLeave() { this.isTransitioning = false; this.order = 0; this.resumeDismiss = this.dismissStarted = 0; var hiddenEvt = this.buildEvent('hidden'); this.emitEvent(hiddenEvt); this.doRender = false; }, makeToast: function makeToast(h) { var _this5 = this; // Render helper for generating the toast // Assemble the header content var $headerContent = []; var $title = this.normalizeSlot('toast-title', this.slotScope); if ($title) { $headerContent.push($title); } else if (this.title) { $headerContent.push(h('strong', { staticClass: 'mr-2' }, this.title)); } if (!this.noCloseButton) { $headerContent.push(h(BButtonClose, { staticClass: 'ml-auto mb-1', on: { click: function click() { _this5.hide(); } } })); } // Assemble the header (if needed) var $header = h(); if ($headerContent.length > 0) { $header = h('header', { staticClass: 'toast-header', class: this.headerClass }, $headerContent); } // Toast body var isLink = this.href || this.to; var $body = h(isLink ? BLink : 'div', { staticClass: 'toast-body', class: this.bodyClass, props: isLink ? { to: this.to, href: this.href } : {}, on: isLink ? { click: this.onLinkClick } : {} }, [this.normalizeSlot('default', this.slotScope) || h()]); // Build the toast var $toast = h('div', { key: "toast-".concat(this._uid), ref: 'toast', staticClass: 'toast', class: this.toastClass, attrs: _objectSpread({}, this.$attrs, { tabindex: '0', id: this.safeId() }) }, [$header, $body]); return $toast; } }, render: function render(h) { if (!this.doRender || !this.isMounted) { return h(); } var name = "b-toast-".concat(this._uid); // If scoped styles are applied and the toast is not static, // make sure the scoped style data attribute is applied var scopedStyleAttrs = !this.static ? this.scopedStyleAttrs : {}; return h(Portal, { props: { name: name, to: this.computedToaster, order: this.order, slim: true, disabled: this.static } }, [h('div', { key: name, ref: 'b-toast', staticClass: 'b-toast', class: this.bToastClasses, attrs: _objectSpread({}, scopedStyleAttrs, { id: this.safeId('_toast_outer'), role: this.isHiding ? null : this.isStatus ? 'status' : 'alert', 'aria-live': this.isHiding ? null : this.isStatus ? 'polite' : 'assertive', 'aria-atomic': this.isHiding ? null : 'true' }) }, [h(BVTransition, { props: { noFade: this.noFade }, on: this.transitionHandlers }, [this.localShow ? this.makeToast(h) : h()])])]); } });