UNPKG

bootstrap-vue

Version:

BootstrapVue, with over 40 plugins and more than 80 custom components, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.

483 lines (452 loc) 14.1 kB
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(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(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, eventOn, eventOff } from '../../utils/dom'; 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; var EVENT_OPTIONS = { passive: true, capture: false }; // --- 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(parseInt(this.autoHideDelay, 10) || 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(newVal) /* istanbul ignore next */ { var _this = this; // If toaster target changed, make sure toaster exists this.$nextTick(function () { return _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 _this2 = this; this.isMounted = true; this.$nextTick(function () { if (_this2.visible) { requestAF(function () { _this2.show(); }); } }); // Listen for global $root show events this.listenOnRoot('bv::show::toast', function (id) { if (id === _this2.safeId()) { _this2.show(); } }); // Listen for global $root hide events this.listenOnRoot('bv::hide::toast', function (id) { if (!id || id === _this2.safeId()) { _this2.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 === _this2.computedToaster) { /* istanbul ignore next */ _this2.hide(); } }); }, beforeDestroy: function beforeDestroy() { this.clearDismissTimer(); }, methods: { show: function show() { var _this3 = 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 () { _this3.localShow = true; }); }); } }, hide: function hide() { var _this4 = 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 () { _this4.localShow = false; }); } }, buildEvent: function buildEvent(type) { var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new BvEvent(type, _objectSpread({ cancelable: false, target: this.$el || null, relatedTarget: null }, opts, { 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 method = on ? eventOn : eventOff; var el = this.$refs['b-toast']; method(el, 'mouseenter', this.onPause, EVENT_OPTIONS); method(el, 'mouseleave', this.onUnPause, EVENT_OPTIONS); }, onPause: function onPause(evt) { // 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(evt) { // 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 _this5 = this; // We delay the close to allow time for the // browser to process the link click this.$nextTick(function () { requestAF(function () { _this5.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 _this6 = 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(evt) { _this6.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()])])]); } });