UNPKG

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

179 lines (169 loc) 5.63 kB
import Vue from '../vue'; import { NAME_TRANSPORTER_SINGLE, NAME_TRANSPORTER_TARGET_SINGLE } from '../constants/components'; import identity from './identity'; import { concat } from './array'; import { removeNode, select } from './dom'; import { isBrowser } from './env'; import { isFunction, isString } from './inspect'; import { HTMLElement } from './safe-types'; import normalizeSlotMixin from '../mixins/normalize-slot'; // BTransporterSingle/BTransporterTargetSingle: // // Single root node portaling of content, which retains parent/child hierarchy // Unlike Portal-Vue where portaled content is no longer a descendent of its // intended parent components // // Private components for use by Tooltips, Popovers and Modals // // Based on vue-simple-portal // https://github.com/LinusBorg/vue-simple-portal // Transporter target used by BTransporterSingle // Supports only a single root element // @vue/component var BTransporterTargetSingle = /*#__PURE__*/Vue.extend({ // As an abstract component, it doesn't appear in the $parent chain of // components, which means the next parent of any component rendered inside // of this one will be the parent from which is was portal'd abstract: true, name: NAME_TRANSPORTER_TARGET_SINGLE, props: { nodes: { // Even though we only support a single root element, // VNodes are always passed as an array type: [Array, Function] // default: undefined } }, data: function data(vm) { return { updatedNodes: vm.nodes }; }, destroyed: function destroyed() { removeNode(this.$el); }, render: function render(h) { var nodes = isFunction(this.updatedNodes) ? this.updatedNodes({}) : this.updatedNodes; nodes = concat(nodes).filter(Boolean); /* istanbul ignore else */ if (nodes && nodes.length > 0 && !nodes[0].text) { return nodes[0]; } else { /* istanbul ignore next */ return h(); } } }); // This component has no root element, so only a single VNode is allowed // @vue/component export var BTransporterSingle = /*#__PURE__*/Vue.extend({ name: NAME_TRANSPORTER_SINGLE, mixins: [normalizeSlotMixin], props: { disabled: { type: Boolean, default: false }, container: { // String: CSS selector, // HTMLElement: Element reference // Mainly needed for tooltips/popovers inside modals type: [String, HTMLElement], default: 'body' }, tag: { // This should be set to match the root element type type: String, default: 'div' } }, watch: { disabled: { immediate: true, handler: function handler(disabled) { disabled ? this.unmountTarget() : this.$nextTick(this.mountTarget); } } }, created: function created() { // Create private non-reactive props this.$_defaultFn = null; this.$_target = null; }, beforeMount: function beforeMount() { this.mountTarget(); }, updated: function updated() { // We need to make sure that all children have completed updating // before rendering in the target // `vue-simple-portal` has the this in a `$nextTick()`, // while `portal-vue` doesn't // Just trying to see if the `$nextTick()` delay is required or not // Since all slots in Vue 2.6.x are always functions this.updateTarget(); }, beforeDestroy: function beforeDestroy() { this.unmountTarget(); this.$_defaultFn = null; }, methods: { // Get the element which the target should be appended to getContainer: function getContainer() { /* istanbul ignore else */ if (isBrowser) { var container = this.container; return isString(container) ? select(container) : container; } else { return null; } }, // Mount the target mountTarget: function mountTarget() { if (!this.$_target) { var container = this.getContainer(); if (container) { var el = document.createElement('div'); container.appendChild(el); this.$_target = new BTransporterTargetSingle({ el: el, parent: this, propsData: { // Initial nodes to be rendered nodes: concat(this.normalizeSlot()) } }); } } }, // Update the content of the target updateTarget: function updateTarget() { if (isBrowser && this.$_target) { var defaultFn = this.$scopedSlots.default; if (!this.disabled) { /* istanbul ignore else: only applicable in Vue 2.5.x */ if (defaultFn && this.$_defaultFn !== defaultFn) { // We only update the target component if the scoped slot // function is a fresh one. The new slot syntax (since Vue 2.6) // can cache unchanged slot functions and we want to respect that here this.$_target.updatedNodes = defaultFn; } else if (!defaultFn) { // We also need to be back compatible with non-scoped default slot (i.e. 2.5.x) this.$_target.updatedNodes = this.$slots.default; } } // Update the scoped slot function cache this.$_defaultFn = defaultFn; } }, // Unmount the target unmountTarget: function unmountTarget() { this.$_target && this.$_target.$destroy(); this.$_target = null; } }, render: function render(h) { if (this.disabled) { var nodes = concat(this.normalizeSlot()).filter(identity); if (nodes.length > 0 && !nodes[0].text) { return nodes[0]; } } return h(); } });