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
193 lines (179 loc) • 6.39 kB
JavaScript
import { Vue, extend, isVue3 } from '../../vue';
import { NAME_TRANSPORTER, NAME_TRANSPORTER_TARGET } from '../../constants/components';
import { IS_BROWSER } from '../../constants/env';
import { PROP_TYPE_ARRAY_FUNCTION, PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../constants/props';
import { HTMLElement } from '../../constants/safe-types';
import { concat } from '../../utils/array';
import { removeNode, select } from '../../utils/dom';
import { identity } from '../../utils/identity';
import { isFunction, isString } from '../../utils/inspect';
import { normalizeSlotMixin } from '../../mixins/normalize-slot';
import { makeProp } from '../../utils/props';
import { createNewChildComponent } from '../../utils/create-new-child-component'; // --- Helper components ---
// BVTransporter/BVTransporterTarget:
//
// 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 BVTransporter
// Supports only a single root element
// @vue/component
var BVTransporterTarget = /*#__PURE__*/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,
props: {
// Even though we only support a single root element,
// VNodes are always passed as an array
nodes: makeProp(PROP_TYPE_ARRAY_FUNCTION)
},
data: function data(vm) {
return {
updatedNodes: vm.nodes
};
},
destroyed: function destroyed() {
removeNode(this.$el);
},
render: function render(h) {
var updatedNodes = this.updatedNodes;
var $nodes = isFunction(updatedNodes) ? updatedNodes({}) : updatedNodes;
$nodes = concat($nodes).filter(identity);
if ($nodes && $nodes.length > 0 && !$nodes[0].text) {
return $nodes[0];
}
/* istanbul ignore next */
return h();
}
}); // --- Props ---
export var props = {
// String: CSS selector,
// HTMLElement: Element reference
// Mainly needed for tooltips/popovers inside modals
container: makeProp([HTMLElement, PROP_TYPE_STRING], 'body'),
disabled: makeProp(PROP_TYPE_BOOLEAN, false),
// This should be set to match the root element type
tag: makeProp(PROP_TYPE_STRING, 'div')
}; // --- Main component ---
// @vue/component
var BVTransporterVue2 = /*#__PURE__*/extend({
name: NAME_TRANSPORTER,
mixins: [normalizeSlotMixin],
props: props,
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 (IS_BROWSER) {
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 = createNewChildComponent(this, BVTransporterTarget, {
el: $el,
propsData: {
// Initial nodes to be rendered
nodes: concat(this.normalizeSlot())
}
});
}
}
},
// Update the content of the target
updateTarget: function updateTarget() {
if (IS_BROWSER && 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) {
// This component has no root element, so only a single VNode is allowed
if (this.disabled) {
var $nodes = concat(this.normalizeSlot()).filter(identity);
if ($nodes.length > 0 && !$nodes[0].text) {
return $nodes[0];
}
}
return h();
}
});
var BVTransporterVue3 = /*#__PURE__*/extend({
name: NAME_TRANSPORTER,
mixins: [normalizeSlotMixin],
props: props,
render: function render(h) {
if (this.disabled) {
var $nodes = concat(this.normalizeSlot()).filter(identity);
if ($nodes.length > 0) {
return $nodes[0];
}
}
return h(Vue.Teleport, {
to: this.container
}, this.normalizeSlot());
}
});
export var BVTransporter = isVue3 ? BVTransporterVue3 : BVTransporterVue2;